Lazy

Ensures a object is initalized once when it is first used.

Java

private var _someProperty: SomeType? = null 
    private val somePropertyLock = Any() 
    val someProperty: SomeType 
    get() { 
        synchronized(somePropertyLock) { 
            if (_someProperty == null) { 
                _someProperty = SomeType() 
            } 
            return _someProperty!! 
        } 
    }

Kotlin

val someProperty by lazy { SomeType() } 

//lazy impl
public fun <T> lazy(initializer: () -> T): 
          Lazy<T> =  SynchronizedLazyImpl(initializer)

The lazy delegate also has a thread safety mechanism. By default, delegates are fully thread safe, but we can change this behavior to make this function more efficient in situations where we know that there never will be more than one thread using it at the same time. To fully turn off thread-safety mechanisms we need to place the enum type value LazyThreadSafetyMode.NONE as a first argument of the lazy function:
val someProperty by lazy(LazyThreadSafetyMode.NONE) { SomeType() }

Thanks to the lazy delegate, the initialization of the property is delayed until the value is needed. Usage of the lazy delegate provides several benefits:

  • Faster class initialization leading to faster application startup time, because value initialization is delayed until they are used for the first time
  • Some values may never be used for certain flow, so they will never be initialized--we are saving resources (memory, processor time, battery)
  • some objects need to be created later, after their class instance is created.
class MainActivity : Activity() { 

    var questionLabelView: TextView? = null 
    var answerLabelView: TextView? = null 
    var confirmButtonView: Button? = null 

    override fun onCreate(savedInstanceState: Bundle) { 
        super.onCreate(savedInstanceState) 
        setContentView(R.layout.main_activity) 

        questionLabelView = findViewById<TextView>(R.id.main_question_label)   
        answerLabelView = findViewById<TextView>(R.id.main_answer_label)
        confirmButtonView = findViewById<Button>(R.id.main_button_confirm)
    } 
}

vs

class MainActivity : Activity() { 

   val questionLabelView: TextView by lazy { findViewById(R.id.main_question_label) as TextView } 
   val answerLabelView: TextView by lazy { findViewById(R.id.main_answer_label) as TextView } 
   val confirmButtonView: Button by lazy { findViewById(R.id.main_button_confirm) as Button } 

   override fun onCreate(savedInstanceState: Bundle) { 
     super.onCreate(savedInstanceState) 
     setContentView(R.layout.main_activity) 
   } 
}

Benfits:

  • The property is declared and initialized in a single place, so the code is more concise.
  • The properties are non-nullable instead of nullable. This prevents lots of useless nullability checks.
  • The properties are read only so thanks to that we have all benefits like threads synchronization or smart casts.
  • The lambda passed to the lazy delegate (containing findViewById) will be executed only when the property is accessed for the first time.
  • Values will be taken later than during class creation. This will speed-up the startup. If we won't use some of these views, their values won't be taken at all (findViewById is not really an efficient operation when the view is complex).
  • Not used property will be marked by the compiler. In Java implementation it won't, because value set would be noticed by the compiler as usage.

We can improve this by:

fun <T: View> Activity.bindView(viewId: Int) = lazy { findViewById(viewId) as T }

Note: There is another way of dealing with this problem. The current popular one is the Kotlin Android Extension plugin, which generates auto-binding to views in Activities and Fragments.

fun <T : Parcelable> Activity.extra(key: String) = lazy 
    { intent.extras.getParcelable<T>(key) } 

fun Activity.extraString(key: String) = lazy 
    { intent.extras.getString(key) }
class SettingsActivity : Activity() { 

    private val doctor by extra<Doctor>(DOCTOR_KEY) // 1 
    private val title by extraString(TITLE_KEY) // 1 

    override fun onCreate(savedInstanceState: Bundle?) { 
        super.onCreate(savedInstanceState) 
        setContentView(R.layout.settings_activity) 
        toast(doctor.id) // 2 
        toast(title) // 2 
    } 

    companion object { // 3 
        const val DOCTOR_KEY = "doctorKey" 
        const val TITLE_KEY = "titleKey" 

    fun start(context: Context, doctor: Doctor, title: String) { // 3 
        ontext.startActivity(getIntent<SettingsActivity>().apply { // 4 
            putExtra(DOCTOR_KEY, doctor) // 5 
            putExtra(TITLE_KEY, title) // 5 
        }) 
    } 
  } 
}
  1. We are defining properties which values should be retrieved from Activity arguments using corresponding keys.
  2. Here we access properties from arguments within the onCreate method. When we ask for property (use getter), the lazy delegate will get its value from extras and store it for later usage.
  3. To make a static method to start activity, we need to use a companion object.
  4. SettingsActivity::class.java is the analogue of Java class reference SettingsActivity.class.
  5. We are using methods defined in Chapter 7, Extension Functions and Properties.

results matching ""

    No results matching ""