State and Configuration Changes

Currently, Coda Pizza has a small problem, and it will instantly be familiar to you. Run Coda Pizza and add a topping or two to the pizza. Then rotate your device or emulator.

Yep. The topping selections are lost after the configuration change. remember persists state across recompositions, but it has its limits: When your Activity is destroyed and re-created, so is your composition. Because the composition is discarded, it will restart from a blank slate when your Activity is re-created and calls setContent.

This was not an issue when you declared the pizza state as a global variable. But now, because your state is associated with your composition hierarchy – and, by extension, your activity – it must obey the rules of the activity lifecycle. Every variable stored using remember will be lost after a configuration change (and process death), just like values stored in your Activity.

In your time with the framework UI toolkit, you saw two approaches to solve this problem: the savedInstanceState bundle and ViewModel. Although ViewModels can be a great tool for managing UI state and can be used in Jetpack Compose, they require more setup than is warranted for your needs in Coda Pizza. savedInstanceState will be your solution, and you will access it using a variation of remember called rememberSaveable.

The saveable portion of this function’s name refers to the fact that any remembered value is also automatically written to your Activity’s savedInstanceState bundle when it is destroyed. Remembered values that were saved can be restored when your composition is re-created, so values remembered in this way also survive configuration changes.

rememberSaveable is called in the same way as remember, using a lambda to perform its initialization. Try it out now. (This change will introduce a problem in your code, which we will explain next.)

Listing 27.15  Remembering and saving (PizzaBuilderScreen.kt)

@Preview
@Composable
fun PizzaBuilderScreen(
    modifier: Modifier = Modifier
) {
    var pizza by rememberSaveable { mutableStateOf(Pizza()) }
    ...
}
...

Run Coda Pizza. It will crash with the following exception:

    IllegalArgumentException: MutableState containing Pizza(toppings={}) cannot
    be saved using the current SaveableStateRegistry. The default implementation
    only supports types which can be stored inside the Bundle. Please consider
    implementing a custom Saver for this class and pass it as a stateSaver
    parameter to rememberSaveable().

Coda Pizza has crashed because it is attempting to write a Pizza to a Bundle. But Bundles are restricted in the types they can store: Only instances of Serializable, Parcelable, and basic types like String, Int, Long, Float, and Double are allowed in a Bundle. To fix this crash, you need to convert Pizza into a type that can be added to a Bundle.

Parcelable and Parcelize

The most effective way to let a Pizza fit into a Bundle is to make it a Parcelable class. Parcelable is an interface provided by the Android OS that allows a class to be converted into and read out of a Parcel object. Parcels allow for compact storage of objects and are ideal for use in a Bundle.

The process of manually implementing the Parcelable interface is a bit complex – plus there are limitations about which data types can appear in the Parcel. Luckily, there is a plugin to help. With a bit of setup, you can have a Parcelable implementation automatically generated for you.

To do this, you will need to add a new plugin to your project called Parcelize, which takes care of generating Parcelable implementations for you at build time. Because Parcelize is a compiler plugin, your Parcelable implementations will always stay up to date with your class definitions, preventing errors when converting a Parcel back into the original object.

To add the Parcelize plugin, first register it with your project by adding its plugin ID and version to your build.gradle file labeled (Project: Coda_Pizza).

Listing 27.16  Adding the Parcelize plugin (build.gradle)

plugins {
    id 'com.android.application' version '7.1.2' apply false
    id 'com.android.library' version '7.1.2' apply false
    id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
    id 'org.jetbrains.kotlin.plugin.parcelize' version '1.6.10' apply false
}
...

Next, apply this plugin to your application by registering it in the app/build.gradle file.

Listing 27.17  Enabling the Parcelize plugin (app/build.gradle)

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'org.jetbrains.kotlin.plugin.parcelize'
}
...

After making these changes to your build configuration files, do not forget to click the Sync Now button to make Android Studio aware of your changes.

After the sync completes, you are ready to make your Pizza class implement the Parcelable interface. With the help of Parcelize, you can accomplish this with two small changes. First, annotate the Pizza class with @Parcelize. Second, make Pizza implement the Parcelable interface.

Listing 27.18  Parcelizing pizza (Pizza.kt)

@Parcelize
data class Pizza(
    val toppings: Map<Topping, ToppingPlacement> = emptyMap()
) : Parcelable {
    ...
}

(If prompted, be sure to choose the import for kotlinx.parcelize.Parcelize instead of kotlinx.android.parcel.Parcelize. The kotlinx.android package is a relic of the now deprecated Kotlin Android Extensions plugin.)

Parcelable contains a few functions that all implementers must define. As you type this code, you may notice that errors about missing function overrides disappear as soon as you annotate the class with @Parcelize. Parcelize will automatically provide the entire implementation for this interface with no extra effort on your part.

Run Coda Pizza. This time, it will not crash, and you will be presented with the familiar list of toppings. Add some toppings to the pizza and rotate the emulator or device. The state should survive the configuration change, and you should see the same selection of toppings – no matter how many times you rotate the phone or what other configuration changes Coda Pizza encounters (Figure 27.2). And because your state is stored in the savedInstanceState bundle, it will even survive process death.

Figure 27.2  Saving pizzas across configuration changes

Saving pizzas across configuration changes

State is a crucial part of any application, and in the world of Compose, it is entirely in your hands. But the discussion of state does not end there. In the next chapter, you will incorporate a dialog into Coda Pizza and see how Compose’s philosophy of state lets you achieve a wide variety of app behaviors.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset