UIKit Apprentice, Second Edition – Now Updated!

Learn iOS and Swift from scratch. Build four powerful apps—with support for iPad and Dark Mode. Publish apps to the App Store.

Home Android & Kotlin Tutorials

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.

5/5 4 Ratings

Version

  • Kotlin 1.4, Android 4.1, Android Studio 4.2

Values and mutability are features all programming languages need to provide. Have you ever had to declare a counter field or save an index of the last selected item in a list? Or did you need to save a license key for a third-party library such as Google Maps?

If the answer is yes, you most likely wondered if you should define those values inside or outside a class and which keywords you need to use. Considering Kotlin has several ways to define values across code, you probably had a hard time finding the best suitable solution.

Finally, here’s a tutorial to address all your doubts! :]

There are three possible ways of creating values in Kotlin — by using var, val and const val before a value name. In this tutorial, you’ll implement them and discover more about the differences between them.

More specifically, you’ll learn:

  • How to declare mutable values, also known as variables, and where to use them
  • How to declare immutable values and where to use them
  • The difference between lazy initialization and late initialization
  • The way to declare a constant and where to use it
  • What a companion object is
  • The difference between static values and inline values
Note: This tutorial assumes a basic knowledge of Android Studio and Kotlin. If you aren’t familiar with Android Studio or you haven’t installed it yet, refer to our Beginning Android Development tutorials.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

In this tutorial, you’ll use the Tempco app. Tempco is a single-screen app for converting temperature values between Fahrenheit, Celsius and Kelvin measurement units.

Open the project in Android Studio. Build and run on a device to see how the app looks. Right now, you’re not able to experiment with conversions, but don’t worry, because you’ll implement all you need to use the app!

The main screen of Tempco.

The app contains a ConstraintLayout with one EditText for inserting the desired temperature value, two Spinner widgets for the initial and final measurement unit, and a TextView for displaying a result. Every element of the layout has a TextView above itself with instructions for use. It’s enough to interact with spinners to see the calculated result.

The app is quite simple, because the focus of the tutorial will be on values and declarations, instead of the Android UI!

Declaring Values

The first step for declaring values is to determine whether to declare it with val or var. What’s the real difference though?

First of all, a var is also known as a variable, something that changes over time, whereas a val is also known as a value, something that doesn’t change over time.

Both variables and values point to values and the terminology can be confusing, but values mean data in this case, and not a property you declare.

That being said, the keyword var is for mutable or non-final data, also knows as variables. You declare variables only if you’ll change the data it points to or the object it references to later.

Meanwhile, the keyword val defines immutable or final values and you can’t change their reference. It similar to the final variable in Java — it’s read-only.

It’s easy to differentiate between var and val, isn’t it? The terminology might be a bit confusing, but you’ll get the hang of it in no time! Now you’ll practice the implementation.

Defining Immutable Values

Open MainViewModel.kt and look for calculate(). This function returns the result of temperature conversions. Add the following line to the top of it:

val temperatureValue: Double = value.toDouble()

Here, you’re defining an immutable value using val. You’re using immutability because the variable is only used as a parameter in conversion — you won’t change it. Then, you’re setting up a return type as Double and assigning the real value, which the user types in EditText.

Now, use this new value in calculate(). Since you already have conversion and finalUnit declared, use them and add the following code below finalUnit:

conversionHelper.getConvertedValue(
  conversion,
  temperatureValue,
  finalUnit,
  ::onSuccess,
  ::onError
)

In this code, you’re using three values and two lambdas using getConvertedValue(). This function uses conversion to find the right formula for the result temperature.

If the calculation succeeds, the system calls onSuccess() and sends back the result. Otherwise, it calls onError() and sends back a String resource to inform the user.

In case you’re wondering if you could use a variable for temperatureValue, the answer is: There’s no need. It’s only used inside calculate(), and you don’t change it.

Defining Mutable Values

Now that you’ve mastered immutability, here’s a mutability task for you! In the same file, replace unitArray with the following code:

var unitArray: Array<String> = emptyArray()

In this line, you declared a variable using var. You set its name to unitArray and the type as an Array of String items, with the default value of an emptyArray().

But why is this a variable? Right now, unitArray is empty because of its default value, but to calculate the result, unitArray needs to contain all the available units.

Consequently, you need to assign an appropriate array of units, and you use a variable to be able to change its value.

There are two reasons why you needed to assign an empty default array. First, the needed array exists in strings.xml. And to use resources, you need a Context. At this place in code, you don’t have access to a Context, as it’s an Android dependency and you shouldn’t mix your business logic with the Android platform.

Second, variables need to be initialized straight away, be abstract, or use lateinit var, which you’ll learn about later.

Data Types and Default Values

When using variables, you need to assign the default value. Otherwise, Android Studio throws an error that the variable either needs to be initialized or it needs to be abstract.

Try this out. Remove the default value of unitArray. Your code will look like this:

var unitArray: Array<String>

You can see the error to set the default value.

The error to define a default value for variable of a primitive data type.

Note: Detailed information about data types is outside the scope of this tutorial. You can read about this in the Kotlin documentation.

Surely you’re wondering what to do if you don’t know which values to set by default, or what to do if you don’t have all the parameters you need at the moment. In the next section, you’ll learn what to do in such situations!

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!

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.

Using Constants in Layouts

Defining constants for layout files is a bit different. Android Studio automatically creates a values folder when creating a project. This folder contains constants divided according to their purpose: dimens.xml contains dimensions, colors.xml contains all colors, styles.xml contains styles and strings.xml contains all text constants.

To test this, open strings.xml and add this line in place of TODO:

<string name="more_about_temperature">More About Temperature</string>

You’re defining a text constant with the name “more_about_temperature” and the value “More About Temperature”.

Then, open colors.xml and add the following in place of TODO:

<color name="link">#0000EE</color>

Here, you’re declaring a color with the name “link” and setting its value to the #0000EE HEX value.

Finally, to use the constants available throughout the project, open activity_main.xml and add this line in TextView with the ID tvAboutTemperature:

  android:text="@string/more_about_temperature"
  android:textColor="@color/link"

That’s it! You just used a constant in a layout file. Build and run to see the difference.

The main screen with constants in the layout file.

Another way to check that these are constants is by accessing them through code. Using the R class, and its string or color children, you refer to constant values that represent the XML resources!

Creating Singletons

When you have a lot of constants connected in one piece of functionality, the best practice for storing them is to use an object declaration (Singleton).

It’s easy to create singletons in Kotlin. All you need to do is use the object keyword. The initialization of object declaration starts on first access, and it’s thread-safe.

Let’s create a singleton. The most common examples are storing API or database keys. In the java/main folder, create a new file, UrlConstants.kt. Add the following code to the file:

object UrlConstants {
  const val DOCUMENTATION_URL = "https://en.wikipedia.org/wiki/Temperature"
}

Here, you declared a singleton with the name “UrlConstants” by using object. Then you added DOCUMENTATION_URL with a URL value inside its body.

You access constants inside object declarations by using the object name. Open MainActivity and find openDocumentation(). Add this to the function body:

startActivity(
  Intent(Intent.ACTION_VIEW, Uri.parse(UrlConstants.DOCUMENTATION_URL))
)

In this code, you used DOCUMENTATION_URL in the form of a Uri, which you used to create an Intent. It’s easier than it sounds, isn’t it?

Distinguishing Object Expressions and Declarations

Keep in mind that object declarations can’t be called object expressions as well. Although it sounds trivial, the difference between them is important. The program executes and initializes object expressions immediately on use, while object declarations are lazily initialized upon first access. Learn more about the two in the official documentation.

Creating Companion Objects

Companion Object is an object declaration inside a class. The program initializes it after loading the corresponding class. For declaring a companion object, use the companion keyword. To call its members, use the class name as the qualifier.

According to the Kotlin documentation on coding conventions, it’s best to place the companion object at the end of a class. Each class can have only one companion object.

Try to implement it. Open ConversionHelper.kt and delete the following values placed on the top of the class to avoid duplicating values:

val ZERO_CELSIUS_IN_KELVIN = 273.15
val ZERO_CELSIUS_IN_FAHRENHEIT = 32
val ZERO_KELVIN_IN_FAHRENHEIT = -459.67
val SCALE = 1.8

Then, add the following code to the bottom of the class:

companion object TempValues {
  const val ZERO_CELSIUS_IN_KELVIN = 273.15
  const val ZERO_CELSIUS_IN_FAHRENHEIT = 32
  const val ZERO_KELVIN_IN_FAHRENHEIT = -459.67
  const val SCALE = 1.8
}

In this block, you created the TempValues companion object and four compile-time constants inside of it. You’re using all of them in temperature calculation. Seeing that there’s no explicit access modifier, these are public constants by default and can be used outside of ConversionHelper.

To prove it, use these constants in MainActivity. Find setUpUI() and the part where you set up tvBaseValue. Replace it with:

binding.tvBaseValue.text = String.format(
   getString(R.string.one_celsius_in_kelvin_and_fahrenheit),
   ConversionHelper.TempValues.ZERO_CELSIUS_IN_KELVIN,
   ConversionHelper.TempValues.ZERO_CELSIUS_IN_FAHRENHEIT,
   ConversionHelper.TempValues.ZERO_KELVIN_IN_FAHRENHEIT
)

In this code, you’re creating the text with the appropriate resource and constants from the companion object you added.

Omitting the Companion Object’s Name

Kotlin lets you define and use a companion object without its name. You’ll come across this case more often. When you do, access the companion object reference by using the Companion keyword instead of its real name. This is necessary outside of the containing class only.

Developers usually use named objects or companion objects to logically group data. That way it’s cleaner and easier to reference or read.

Now, notice Android Studio shows a warning of redundant references in MainActivity.

The error of redundant references.

Try to remove the companion object’s name. Delete the TempValues name and remove redundant references as Android Studio suggests. Your code will look like this:

companion object {
  const val ZERO_CELSIUS_IN_KELVIN = 273.15
  const val ZERO_CELSIUS_IN_FAHRENHEIT = 32
  const val ZERO_KELVIN_IN_FAHRENHEIT = -459.67
  const val SCALE = 1.8
}
binding.tvBaseValue.text = String.format(
   getString(R.string.one_celsius_in_kelvin_and_fahrenheit),
   ConversionHelper.ZERO_CELSIUS_IN_KELVIN,
   ConversionHelper.ZERO_CELSIUS_IN_FAHRENHEIT,
   ConversionHelper.ZERO_KELVIN_IN_FAHRENHEIT
)

There’s no warning anymore! This demonstrates that you can call a companion object’s members by using only the class name. It doesn’t matter if the companion object has a name or not!

Build and run. Test conversions and try accessing the documentation to learn about temperature by clicking More About Temperature.

Browser site about temperature.

Using Static Values and Inlining

Kotlin doesn’t contain a special keyword for defining static fields. Whenever you want a static value, define it inside the companion object. The compiler turns values inside of the companion object into static fields in the bytecode. This means that those values exist as members of the class, but not as members of the class’s instance.

Default Modifiers for Values in Companion Object

To prove these statements, navigate to the Android Studio toolbar and open ToolsKotlinShow Kotlin Bytecode. You’ll see a new window on the right side of Android Studio. The Kotlin Bytecode tool presents Java Virtual Machine (JVM)-compatible instructions for a certain Kotlin class.

Kotlin Bytecode Window

Now, open ConversionHelper.kt to the left of the bytecode window and choose Decompile. The bytecode is generated automatically in a new Java class.

Decompiled ConversionHelper.

After decompiling, notice the modifiers next to the values in ConversionHelper.kt‘s companion object. All of them have public, final and static modifiers. You can conclude that marking classes and methods with the final modifier is the default. Since getConvertedValue() was outside of companion object, it isn’t marked as static.

Exposing Kotlin Static Properties in Java

The previous approach of defining properties is OK if you’re using Kotlin only. However, what if you need to use the companion object’s static values or methods from Java files? The answer is JVM annotations.

To make a property recognizable by the Java compiler, add a @JvmField annotation before the constant you want to use. In the case of a static method, add @JvmStatic before fun.

Note: If you’re interested in Kotlin-Java interop, check out Kotlin-Java interop in the Android documentation and static fields and methods in the Kotlin documentation.

Inlining Properties

You’re almost at the end! With technology growing, developers can experience huge runtime vulnerability because of memory allocation. The big problem lies in reserving space for functions, as a function is an object with a closure. And the closure is never empty — in 99 percent of the time, it contains a bunch of variables. Because of that, you can use inlining to save memory! The keyword for this is inline, and it needs to be set before fun.

When inlining properties, you have to inline their accessors. You can inline accessors individually, or you can place the keyword before the entire property to inline all accessors. As a result of mutability rules, for immutable properties you can only inline get().

Try to use it. Open MainActivity.kt and replace TAG with:

val TAG: String?
  inline get() = MainActivity::class.qualifiedName

In this block, you’re defining an immutable value that sets the Activity’s qualified name using an inline custom getter. For this example, you can’t inline the setter because of its mutability setting.

Now, find TODO in onItemSelected() and replace it with the following:

Log.d(TAG, "Selected item: ${viewModel.unitArray[p2]}")

Here you’re logging a selected item value in the console with a defined tag.

Run decompiling in the Kotlin bytecode tool. Press Decompile, and when the generated file opens, scroll to the place where you use TAG. Notice that with inline, the system evaluates operations at the call site. If you have a more complex expression in your application, use inlining properties for small performance optimizations.

Inlining Functions

When inlining functions, the most common use is on higher-order functions with lambda parameters. This avoids extra object creation of functional parameters because the system omits the method call and moves the method code to the call site. Consequently, the memory is less jammed.

Now, try inlining on a function. Open ConversionHelper.kt and add inline before getConvertedValue():

inline fun getConvertedValue(
  conversion: Conversion,
  temperatureValue: Double,
  finalUnit: String,
  onSuccess: (Double, String) -> Unit,
  onError: (Int) -> Unit
)

Here you’re only setting up getConvertedValue() to execute where you call it.

Build and run for the last time! Although you won’t experience any new functionality, play a bit with conversions to check that there are no bugs.

The main screen with all functionallities.

Where to Go From Here?

Download the final project by clicking the Download Materials button at the top or bottom of the tutorial.

Nice work on this tutorial! You’ve learned the best practice for declaring constants, as well as the importance of the companion object and singleton. Also, you’ve practiced how to define inline properties and how to clone Java static behavior in Kotlin.

If you’re wondering how to use collections and mutability, refer to this Kotlin Collections tutorial.

To learn more about the companion object, check out this Companion Objects video course.

If you’re interested in inlining, see the Kotlin documentation on inline functions and properties.

Hopefully, you’ve learned a lot and had fun! :]

If you have any questions or comments, please join the forum discussion below!

Average Rating

5/5

Add a rating for this content

4 ratings

More like this

Contributors

Comments