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 3 of 4 of this article. Click here to view the first page.

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.