Do you have properties in your Kotlin types that you want to create later after initialization? You may want to avoid making them nullable, avoid using lateinit
or delay an expensive set up process to a later point. In Kotlin this is where delegated properties and lazy properties in particular can be used, allowing properties to be initialized when they are needed and even keep our main type's initialization simpler and cleaner.
The idea of making a property lazy is not new to Kotlin and many developers having been doing it in other languages or doing it themselves for ages. There were common approaches to a form of lazy creation in Java, it was hard to live in the Java world without coming across a getInstance()
function at some point!
When it comes to defining lazy properties in Kotlin, they have been brought into being part of the language. There is some new syntax, some different options for initializing them and various situations in which they can be really useful. Let's take a look!
Creating them
Creating a lazy property in Kotlin is pretty simple, we define it using by lazy
and provide a function initializer. At a later point, when the property is first accessed it will be initialized using the function we provided and then on future accesses the cached value will be returned instead. Nifty!
private val bindingController by lazy {
FilesBindingController(viewModel::onFileOpened)
}
Our bindingController
property needs to access the viewModel
property in order to be created and so by making it lazy, this can be deferred until it is first used. After bindingController
has been initialized, the value will simply be returned every time from then on.
Factory method
Creating our property may not always be as simple as just calling an initializer and more code may be needed to set it up. In these situations we may wish to move the contents of the lazy function initializer to a factory method on the outer class.
class TeamRepository(appSchedulers: AppSchedulers) {
private val viewState by lazy(::createViewStateLiveData)
private fun createViewStateLiveData(): LiveData<ViewState> =
teamRepository.teamMembersStream()
.map(::mapPresentingState)
.onErrorReturn(::mapErrorState)
.startWith(ViewState.Loading)
.subscribeOn(appSchedulers.io)
.observeOn(appSchedulers.main)
.toLiveData()
}
Moving the initialization code to a factory method can keep the top of our class cleaner and we may find we prefer to keep this set up code out of the way. In some situations we may even be able to use a separate factory object to move the initialization logic to another type or file if we wanted to.
Extension
Across our codebase there will be common situations for using lazy properties and so to simplify the call sites we may decide to use an extension function. For example, when using Android ViewModel
, each of our Activities will need to retrieve their View Model, which can be done using an extension function on a common base class.
inline fun <reified ViewModelT : ViewModel> ComponentActivity.bindViewModel() =
bindViewModel(ViewModelT::class, viewModelFactoryProvider)
@PublishedApi
internal fun <ViewModelT : ViewModel> ComponentActivity.bindViewModel(
viewModelType: KClass<ViewModelT>
): Lazy<ViewModelT> = lazy {
ViewModelProvider(this).get(viewModelType.java)
}
Using our new lazy extension function cleans up the initialization call site.
class TeamActivity : FragmentActivity() {
private val viewModel: TeamViewModel by bindViewModel()
}
This particular example is now provided by the AndroidX Activity library.
How does it work?
Delegated properties work as their name suggests, delegating the property's getter and maybe setter to another type. There are a selection of different delegated property types provided and then on top of this we can write our own. The expression we place after the by
keyword is what specifies the delegate that will be used.
In the case of by lazy
, a Lazy
delegate type will control access to our property, initializing it on first access and then returning the cached value on subsequent accesses. Users of the property don't need to change how they call it, meaning the read-write behaviour of our property can be changed at the property definition without affecting code that uses it.
Multithreading
The lazy
function has an argument with a default value that controls its synchronization behaviour. If a lazy property is accessed from multiple threads concurrently, synchronization will need to be handled by choosing an appropriate LazyThreadSafetyMode
.
private val messageId by lazy(LazyThreadSafetyMode.NONE) { createMessageId() }
The default value SYNCHRONIZED
will ensure only a single thread can initialize the property using locks. If we are sure the property will only be accessed by a single thread we can switch to NONE
to avoid the overhead of performing the synchronization. There is also the option of using PUBLICATION
which allows multiple threads to call the initializer, but only the first returned value being used.
Most UI code, such as in an Activity
or Fragment
, will run on the UI thread and so properties that are only used here can use the LazyThreadSafetyMode.NONE
. We could even add an extension to avoid specifying this each time.
Other use cases
Lazy is useful wherever we want to delay initialization to a later point, rather than doing it straight away. Without it other options may include a lateinit
property which simply "promises" to be initialized before it is used or a nullable property which can have a value or not.
When we just want to delay initialization, lazy can be a nicer approach to the alternatives. It can be especially helpful for obtaining dependencies whose initialization is out of our control, such as Android View Models from an Activity or Fragment.
There are times when we need to read a value from somewhere, but don't want it to be retrieved again each time it is needed. An example is reading Bundle
extras from an Intent
, where using a lazy property would mean on first access the value is read from the Intent
and then cached for quicker access in the future.
class OrderDetailActivity : FragmentActivity() {
val orderId by lazy {
intent.getParcelableExtra<OrderId>(EXTRA_ORDER_ID)
}
}
Wrap up
Delegated properties and lazy properties in particular are a great feature to have at our disposal. The give us flexibility over how our properties are initialized and accessed. It is really nice to be able to make a property lazy without having to alter how it is used in calling code.
I hope the article was useful. If you have any feedback or questions please feel free to reach out.
Thanks for reading!
WRITTEN BY
Andrew Lord
A software developer and tech leader from the UK. Writing articles that focus on all aspects of Android and iOS development using Kotlin and Swift.
Want to read more?
Here are some other articles you may enjoy.