Values and Mutability In Kotlin: Getting Started

In this Values and Mutability tutorial, you’ll learn how to declare mutable, immutable, constant, late/lazily initialized, static & inline values. By Gabriela Kordić.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Using Late Initialization

Late initialization enables setting a variable sometime in the future and not immediately when declaring it. This is efficient because you don’t take up memory with empty or default values. Instead, you initialize your values later, when you’re ready to do it.

One common example of late initialization is when you can’t pass parameters to the constructor and have to create them after some time.

The keyword for late initialization is lateinit var. As you’re able to set the variable at any time or stage, this type of initialization is always connected with mutable values. That being said, the values can’t be nullable and you can’t use late initialization with primitive data types, only complex objects.

Use the code to verify this mutability statement. There’s an existing error created in the previous section. To fix it, replace unitArray with the following:

lateinit val unitArray: Array<String>

You can see that Android Studio shows a mutability error, as you’ve set the variable as immutable. This action breaks the main rule for late initialization: it doesn’t use variables.

A mutablity error shown in Android Studio.

To correct this error, change the keyword of unitArray from val to var. Your code will look like this:

lateinit var unitArray: Array<String>

There’s a reason why developers don’t use late initialization often. If you access the variable before it’s initialized, you get a runtime exception, and the app crashes not knowing what to do — the value is neither set nor is it null, it doesn’t exist at all!

The Importance of Initializing Properties Before Use

Try to reproduce this crash. Open Logcat in Android Studio and choose Error in the mode dropdown.

Logcat error mode

Build and run. Check the console output and you’ll find UninitializedPropertyAccessException.

An UninitializedPropertyAccessException error.

To fix this error, open MainActivity.kt, find setUpUI() and add this code to it:

viewModel.unitArray = resources.getStringArray(R.array.temperature_units)

You’ve assigned the resource value to unitArray. Notice you did it externally; you assigned the value in Activity, but you declared the variable in ViewModel.

The most common scenario for late initialization is if you’re using dependency injection, or DI, frameworks like Dagger or Hilt. You have to use lateinit var for defining all the dependencies you want to inject.

Since you can’t initialize a variable provided with DI, you need to ensure this will be handled at a later stage. This is one of the main reasons why Kotlin developers introduced lateinit.

Another good example of use is when you need to create objects that require a complex lifecycle and set them up in a non-blocking way (e.g. games, graphic apps, server-side init).

Finally, you can use lateinit when you want to avoid nullable types.

Whenever you use it, keep in mind it’s important to assign the proper value before using the variable. Since Kotlin 1.2, it’s possible to check if a lateinit var is initialized by using ::variableName.isInitialized. However, it’s better to use a nullable value instead. Then, you need to deal with a bunch of null checks, but at least you don’t have to worry about crashing the app!

The conclusion is that late initialization is the last option you should think of when defining a variable. Use lazy initialization or regular values instead.

Build and run. Try to open dropdowns and choose one of the measuring units.

Populated measurment unit dropdowns.

Using Lazy Initialization

You’ve already learned how to initialize a variable whenever you want. However, that makes the variable mutable, but what if you don’t want that? Maybe you want a safer solution, like assigning a value only once and reusing that instance every time you need it.

Here comes lazy initialization. Use the lazy property delegate when you don’t want to initialize a value until you need it. This helps you save memory until you use the value!

A lazy value will build the value provided inside a lambda function only once — when it’s first used. As you can guess, for this type of initialization, you’ll use immutable values.

Now, implement one lazy value. Open MainActivity and find an existing viewModel value. Notice that this is a bad way of initializing instances of ViewModel, since you should create the ViewModel only once and reuse the instance.

Use the following code instead:

private val viewModel: MainViewModel by lazy {
  ViewModelProvider(this).get(MainViewModel::class.java)
}

You’re creating a lazy value of MainViewModel. Using by lazy ensures that ViewModelProvider will create instance of MainViewModel when it’s used for the first time. Furthermore, because of ViewModelProvider, the ViewModel instance will persist through configuration changes, and it won’t be overwritten.

Note: To read more about ViewModelProviders, read the ViewModelProviders documentation.

If you’re wondering if you could use lateinit here, you could, but it’s a lot riskier! In most cases, lateinit can replace lazy. Yet, the reverse isn’t possible.

However, it’s best to use lazy whenever possible. It’s safer because of its own encapsulation, and it’s a cleaner way of defining values since you have all the code in one place, which isn’t the case with lateinit var.

Another great thing about lazy is that if you never access the variable, it won’t use up memory because it won’t be initialized at all!

For more information, see Handling Lifecycles with Lifecycle-Aware Components.

Note: Maybe you’re wondering how to connect lazy values with the Android lifecycle. Android Architecture Components contain a lazy property delegate which is aware of the lifecycle, lifecycleAwareLazy(), as it receives the lifecycle instance as a parameter. The property delegate assures clearing the value during onStop(). This way, memory leaks aren’t possible. This approach is useful in Fragments!

For more information, see Handling Lifecycles with Lifecycle-Aware Components.

Declaring Constants

Now, the real fun starts! :]

Constant values, as the name suggests, are actually immutable values, and you can define it with both val and const val. So how can you differentiate between them?

Defining read-only properties with const marks them as Compile-Time Constants and can only represent primitive types and Strings. But, you can read their values during compile time. Alternatively, you can read vals only in runtime.

When defining compile-time constants, consider these rules:

  • Constants have to be top-level properies, or defined in an object declaration or a companion object.
  • They can contain only Strings or primitive values.
  • A custom getter isn’t allowed.
Note: You can use a compile-time constant in annotations as well. Check out examples in the Kotlin documentation on annotations.

Investigate these constant types. Start with defining val by opening MainActivity.kt. Below the class declaration, add this line:

val TAG = MainActivity::class.qualifiedName

TAG is a value that will always contain a qualified name of MainActivity and you’ll use it for logging purposes. This is the usual way of setting a tag for logging. You can’t define it as a compile-time constant because the qualified name returns an optional String.

Open MainViewModel. Below the class declaration, find CELSIUS_UNIT, KELVIN_UNIT and FAHRENHEIT_UNIT and add the const keyword to each of them:

const val CELSIUS_UNIT = "°C"
const val KELVIN_UNIT = "K"
const val FAHRENHEIT_UNIT = "°F"

You declared three compile-time String constants. Notice that Android Studio is showing an error on const because you didn’t fulfill the first compile-time constant rule.

The error on const modifier.

To fix it, move these three lines above the class declaration to make them top-level constants. From now on, these constants are public and accessible anywhere from the file. Now that you fulfilled all the rules, the error is no longer showing!

Constants are really useful for String values, which describe various keys that you use for logging, authentication, printing data and analytics.