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ć.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Values and Mutability In Kotlin: Getting Started
30 mins
- Getting Started
- Declaring Values
- Defining Immutable Values
- Defining Mutable Values
- Data Types and Default Values
- Using Late Initialization
- The Importance of Initializing Properties Before Use
- Using Lazy Initialization
- Declaring Constants
- Using Constants in Layouts
- Creating Singletons
- Distinguishing Object Expressions and Declarations
- Creating Companion Objects
- Omitting the Companion Object’s Name
- Using Static Values and Inlining
- Default Modifiers for Values in Companion Object
- Exposing Kotlin Static Properties in Java
- Inlining Properties
- Inlining Functions
- Where to Go From Here?
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.
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.
Build and run. Check the console output and you’ll find UninitializedPropertyAccessException
.
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.
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.
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.
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 String
s. But, you can read their values during compile time. Alternatively, you can read val
s 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
String
s or primitive values. - A custom getter isn’t allowed.
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.
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.