State persistence and recovery of Android Jetpack Compose

1 Overview

In the previous article, we mentioned remember. We all know that remember can cache the creation status to avoid loss due to reorganization. Although the state cached using remember can span reorganization, it cannot span activities or processes. For example, when a ConfigurationChanged event such as horizontal and vertical screen switching occurs, assuming that the corresponding onConfigurationChanged function is not rewritten, the Activity will be destroyed and rebuilt, causing the state saved by remember to be lost. To solve this problem, Compose provides rememberSavable, which can automatically save the state when the process is killed like Activity's onSaveInstanceState, and restore it as the process is rebuilt like onRestoreInstanceState.

2.Example analysis

The data in rememberSavable will be saved with onSaveInstanceState, and restored to the corresponding Composable according to the key when the process or Activity is rebuilt. This key is the unique identifier of the Composable determined during compilation. Only when the user manually exits, the data in rememberSavable will be saved.

The implementation principle of rememberSavable is actually to save data in the form of Bundle, so all basic data types supported by Bundle can be automatically saved. If we are saving an object, it needs to be turned into a Parcelable object. For example, if we want to save a Person class, the code is as follows:

@Parcelize
data class Person(
    val name: String,
    val age: String
) : Parcelable

当为一个Parcelable接口的派生类添加@Parcelable时,Kotlin编译器会自动为其添加Parcelable的实现,使用@Parcelable注解时,需要添加kotlin-parcelize插件,在模块的build.gradle文件中对应地方添加如下的代码

plugins{
    
    
	id 'kotlin-parcelize'
}

The code for the test is as follows:

class ComposeCounterAct : ComponentActivity() {
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContent {
    
    
            MyComposeTheme {
    
    
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
    
    
                    GetPerson()
                }
            }
        }
    }

    @Composable
    fun GetPerson() {
    
    
        val person = rememberSaveable(key = "zxj") {
    
    
            mutableStateOf(Person("walt", 29))
        }

        Column {
    
    
            ShowText(person.value)
            Button(modifier = Modifier.wrapContentWidth().height(40.dp), onClick = {
    
    
                person.value = (Person("zhong", 30))
            }) {
    
    
                Text("修改数据!!!")
            }
        }
    }

    @Composable
    fun ShowText(person: Person) {
    
    
        Text(text = "name: ${
      
      person.name}===>${
      
      person.age}")
    }
}

@Parcelize
data class Person(
    var name: String,
    var age: Int
) : Parcelable

In the above code, we used rememberSaveableto save our Personobject. We gave an initial value at the beginning, mainly for demonstration rememberSavablepurposes. We set up a button. After clicking the button, person对the corresponding properties of the image will be modified. At this time If we let the current activityone be destroyed and rebuilt by the system, we will find that our modified value will not be reset to the default value, but if used remember, the value will be reset. So how to simulate the current page being recycled by the system? onConfigurationChanged(newConfig: Configuration)There are two methods provided here. One is to rotate your phone without overriding the Activity method. At this time, Activityit will be destroyed and rebuilt. The second method is to turn on the switch of not retaining background activities in the development options, then exit the application and then enter again. Take Honor mobile phone as an example:

Insert image description here

建议读者可以自己动手验证下,这里需要注意的是保留的数据能恢复只是在当前页面被系统回收的情况下会自动恢复,而用户手动退出,异常皆不会恢复之前的数据

#3. Customize Saver
As we mentioned in the previous section, the class that saves data must be a Parcelable class, but some data structures may not be able to add the Parcelable interface, such as some classes defined in third-party class libraries. For these classes, we can use a custom Saver to implement save and restore logic. We only need to pass in this Saver when calling rememberSavable. The code is as follows:

class ComposeCounterAct : ComponentActivity() {
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContent {
    
    
            MyComposeTheme {
    
    
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
    
    
                    GetPerson()
                }
            }
        }
    }

    @Composable
    fun GetPerson() {
    
    
        var person = rememberSaveable(stateSaver = PersonSaver) {
    
    
            mutableStateOf(Person("walt",29))
        }

        Column {
    
    
            ShowText(person.value)
            Button(modifier = Modifier.wrapContentWidth().height(40.dp), onClick = {
    
    
                person.value = (Person("zhong", 30))
            }) {
    
    
                Text("修改数据!!!")
            }
        }
    }

    @Composable
    fun ShowText(person: Person) {
    
    
        Text(text = "name: ${
      
      person.name}===>${
      
      person.age}")
    }

}

object PersonSaver:Saver<Person,Bundle>{
    
    
    override fun restore(value: Bundle): Person? {
    
    
        return value.getString("name")?.let {
    
    
            name->
            value.getInt("age").let {
    
     age->
                Person(name,age)
            }
        }
    }

    override fun SaverScope.save(value: Person): Bundle? {
    
    
       return Bundle().apply {
    
    
           putString("name",value.name)
           putInt("age",value.age)
       }
    }
}

data class Person(
    var name: String,
    var age: Int
)

As shown in the code above, we removed Person's Parcelable implementation, and then used a custom Saver to complete state persistence and recovery. The results are the same as in the previous section.

4. MapSaver and ListSaver provided by Compose

4.1 mapServer

MapSaver converts the object into a Map<String,Any> structure and saves it. Note that value is a type that can be saved to a Bundle.
code show as below:

class ComposeCounterAct : ComponentActivity() {
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContent {
    
    
            MyComposeTheme {
    
    
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
    
    
                    GetPerson()
                }
            }
        }
    }

    @Composable
    fun GetPerson() {
    
    
        var person = rememberSaveable(stateSaver = personSaver) {
    
    
            mutableStateOf(Person("walt",29))
        }

        Column {
    
    
            ShowText(person.value)
            Button(modifier = Modifier.wrapContentWidth().height(40.dp), onClick = {
    
    
                person.value = (Person("zhong", 30))
            }) {
    
    
                Text("修改数据!!!")
            }
        }
    }

    @Composable
    fun ShowText(person: Person) {
    
    
        Text(text = "name: ${
      
      person.name}===>${
      
      person.age}")
    }

}

val personSaver = run {
    
    
    val nameKey = "name"
    val ageKey = "age"
    mapSaver(
        save = {
    
     mapOf(nameKey to it.name,ageKey to it.age) },
        restore = {
    
    Person(it[nameKey] as String,it[ageKey] as Int)}
    )
}

data class Person(
    var name: String,
    var age: Int
)

4.2 ListSaver

ListSaver converts objects into List data structures for saving. You can replace the above saver part with the following code. The result will be the same, but the method of use is different. It is recommended that readers choose according to the situation.

val personListSaver = listSaver<Person,Any>(
    save = {
    
     listOf(it.name,it.age) },
    restore = {
    
    Person(it[0] as String,it[1] as Int)}
)

注意: 假设只是需要状态跨越Configurationchagned而不需要跨进程恢复,那么我们可以在Androidmanifest.xml中设置android:configChanges,然后使用普通的remember即可。因为Compose能够在所有ConfigurationChanged发生时做出响应。但是理论上纯Compose项目是不需要因为ConfigurationChange重建Activity的,这里希望读者正确区分

Guess you like

Origin blog.csdn.net/zxj2589/article/details/132735513