Kotlin

Exploring The Differences Between Kotlin By Lazy and Lateinit Delegate

It’s been three years since the official release of Kotlin programming language, but people like me still trying to figure out when to use lateinit delegate and by lazy. The lateinit and by lazy are important property initialization feature. We must know when to use which property initialization.

You may wanna read this article on Kotlin Lazy Evaluation.

Before to start showing some of the examples of late initialization here are some significant differences between lateinit var delegated property and by lazy method.

lateinit varby lazy
Can be initialized from anywhere the object seen from. Can only be initialized from the initializer lambda.
Multiple initialization possible.Only initialize single time.
Non-thread safe. It’s up to user to initialize correctly in a multi-threaded environment. Thread-safety by default and guarntees that the initializer is invoked by once.
Can only be used for var. Can only be used for val.
Not eligible for nonnull properties.Not eligible for nonnull properties.
An isInitialized method added to check whether the value has been initialized before.Property never able to un-initialized.
Not allowed on properties of primitive types.Allowed on properties of primitive types.

Lazy Pattern

The by lazy is like a singleton pattern, if->null->then->init->else->returnValue and the lambda executed only if the variable is called for the first time.

Now let’s see a simple example first.

class UserManager private constructor() {

    companion object {
        private val userManager = UserManager()
        fun getInstance() = userManager
    }
}

private val userManager : UserManager by lazy {
    println("Initializing...")
    UserManager.getInstance()
}

fun main() {
    println(userManager)
    println(userManager)
}

In Kotlin the lazy() is a function that takes a lambda and returns an instance of lazy<T>. In our case, it returns lazy<UserManager>. The variable will not be initialized unless you use that variable in your code. You can see that in the output after you hit the run button the _userManager lazy delegate only initialize the first time and after that, it returns the same value.

The by lazy is a very good approach if we’re trying to access system services in Android Activity or Fragment class.

class MainActivity : AppCompatActivity {

   private val connectivityManager: ConnectivityManager by lazy {
        getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    }

    override fun onCreate(savedInstanceState : Bundle) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_main)
         val netweorkInfo  = connectivityManager.activeNetworkInfo
    }
}

The above code works perfectly because we’re trying to access the lazy delegate inside the onCreate method of an Activity.

Let’s see another example of by lazy delegate.

class MainActivity : AppCompatActivity {

   private val users = mutableListOf<User>()

   private val _myAdapter: MyAdapter by lazy {
        MyAdapter(context : this, data : users)
    }

    override fun onCreate(savedInstanceState : Bundle) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_main)
         recyclerView.adapter = _myAdapter
    }
}

Note: The by lazy is not good for referencing views in a Fragment. Because of the common pattern of configuring views inside onCreateView cause a crash. They can be used if view configuration is done in onViewCreated.

LATEINIT PATTERN 

If we want to delay the initialization process of a non-null property then we can happily use the lateinit keyword. The lateinit in kotlin is a keyword and we can use it as follows:

class MediaHelper private constructor(){

    private lateinit var mediaRepo : MediaRepo
 
    init {
        mediaRepo = getMediaRepo()
    }

    private fun getMediaRepo() : MediaRepo {
         .....
         .....
    }
}

In general, properties declared as non-null with lateinit can be declared anywhere in the project. But before using the property you need to initialize it, not so you get the Caused by:kotlin.UninitializedPropertyAccessException: lateinit property has not been initialized. In our case, we’re initializing the mediaRepo in the class initializer block. If you want to inject the properties via dependency injection you need to use the lateinit keyword on a property.

class MediaHelper {

   @Inject
   lateinit var mediaRepo : MediaRepo

   init {
       DaggerAppComponent
           .builder()
           .build()
           .inject(this)              
   }

   fun extractMedia(mediaId: String) : Media {
        return mediaRepo.get(mediaId)
   } 
}

Like we see there are several ways to use lateinit, let’s see another example of view initialization in Fragment.

class LoginFragment : Fragment() {

    private lateinit var email : TextView
    private lateinit var password : TextView

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) : View? {
         val view = layoutInflater.inflate(R.layout.fragment_user, container, false)
         email = view.findViewById(R.id.email)
         password = view.findViewById(R.id.password)
         return view
    }
}

Note: It makes more sense to use lateinit for referencing views inside Activity and Fragment. The downside is we have to ensure they are initialized in the appropriate lifecycle methods.

Protip: You can also check that the property of lateinit is initialized with the isInitialized method.

lateinit var lazyValue : String

fun main() {
    if(::lazyValue.isInitialized)
        println("Initialized...")
    else
        println("Not initialized...")
}

What’s Next

Next, we’ll explore the Lazy evaluation in Kotlin with ThreadSafetyMode’s. Stay tuned!

I hope you guys enjoyed the difference between lateinit and by lazy, and if you have any questions, comments or any modifications to this article please let me know via comments.

Thank you for being here and keep reading…

1 Comment

Write A Comment