A state in an application refers to any value that can change over time. This is a very broad definition, covering all variables from Room databases to classes.
Since Compose is a declarative UI that updates the UI according to state changes, the processing of state is crucial. You can simply understand the state here as the data displayed on the page, so state management is to process the reading and writing of data.
remember
It is used to save state, let’s give a small example below.
@Composable fun HelloContent() { Column(modifier = ()) { OutlinedTextField( value = "", onValueChange = { }, label = { Text("Name") } ) } }
For example, we added an input box to the page. If it is just processed in the above code, you will find that the text we entered will not be recorded, and the input box is always empty. This is because of the attributevalue
Fixed to an empty string. We useremember
Optimize it:
@Composable fun HelloContent() { val inputValue = remember { mutableStateOf("") } Column(modifier = ()) { OutlinedTextField( value = , onValueChange = { = it }, label = { Text("Name") } ) } }
passonValueChange
Update value,mutableStateOf
Will create observableMutableState<T>
, When value is changed, the system will reorganize all read valueComposable
function, this will automatically update the UI.
Jetpack Compose does not mandate you to use MutableState to store state. Jetpack Compose supports other observable types. Before you read other observable types in Jetpack Compose, you must convert them to a State so that Jetpack Compose can automatically reorganize the interface when the state changes.
-
LiveData
Extended functions can be usedobserveAsState()
Convert to State. -
Flow
Extended functions can be usedcollectAsState()
Convert to State. -
RxJava
Extended functions can be usedsubscribeAsState()
Convert to State.
Althoughremember
Helps you stay state after reorganization, but won't help you stay state after configuration changes. To do this, you must userememberSaveable
。rememberSaveable
Any value that can be saved in the Bundle is automatically saved.
As for the example above, if we rotate the screen, we will find that the text in the input box will be lost. You can use it nowrememberSaveable
replaceremember
To help us restore the interface state.
Since the saved data isBundle
Therefore, there are limitations on the types of data that can be saved. For example, basic types, String, Parcelable, Serializable, etc. Generally speaking, add a saved object@Parcelize
Annotations can solve the problem.
If some reason does not work@Parcelize
, you can usemapSaver
Custom rules that define how objects are saved and restored to a Bundle.
data class City(val name: String, val country: String) val CitySaver = run { val nameKey = "Name" val countryKey = "Country" mapSaver( save = { mapOf(nameKey to , countryKey to ) }, restore = { City(it[nameKey] as String, it[countryKey] as String) } ) } @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
If you find it troublesome to define the key of a map, you can use itlistSaver
and index it as a key.
data class City(val name: String, val country: String) val CitySaver = listSaver<City, Any>( save = { listOf(, ) }, restore = { City(it[0] as String, it[1] as String) } ) @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
3. Status improvement
For the aboveremember
orrememberSaveState
Method to save stateComposable
Functions, we call stateful. The advantage of being stateful is that the caller does not need to control the state and does not have to manage the state itself. However, the internal stateComposable
It is often not easy to reuse and is more difficult to test.
In development of reusableComposable
When you usually want to provide the sameComposable
stateful and stateless versions of . The stateful version is convenient for callers who don't care about states, while the stateless version is necessary for callers who need to control or promote states.
State elevation in Compose is a mode that moves state to the caller to make composable items stateless.
Give an example to illustrate the status improvement. For example, we implement a Dialog. In order to facilitate the use of the text displayed in it, we can write the click event logic to the inside of the dialog to encapsulate it. Although it is simple to use, it is not universal. So for the sake of generalization, we can pass in the callback of the text and click event as parameters, which makes it flexible.
State improvement is actually such a programming idea, it has just changed the noun and nothing special.
For the example in the input box above, we use the status prompt to optimize it:
@Composable fun HelloScreen() { var name by rememberSaveable { mutableStateOf("") } HelloContent(name = name, onNameChange = { name = it }) } @Composable fun HelloContent(name: String, onNameChange: (String) -> Unit) { Column(modifier = ()) { OutlinedTextField( value = name, onValueChange = onNameChange, label = { Text("Name") } ) } }
This is doneComposable
The function HelloContent is decoupled from the storage method of state, which is convenient for us to reuse.
This pattern of state declines and events rises is called "one-way data flow". In this case, the state will drop from HelloScreen to HelloContent and the event will rise from HelloContent to HelloScreen. By following a one-way data stream, you can decouple the composable items that display states in the interface from the partial storage and changing states in the application.
4. Status Management
Depending on the complexity of the composable terms, different alternatives need to be considered:
Use Composable as a trusted source
Used to manage simple interface element states. For example, mentioned in the previous articleLazyColumn
Scroll to the specified item and place the interactions in the currentComposable
Progress in.
val listState = rememberLazyListState() val coroutineScope = rememberCoroutineScope() LazyColumn( state = listState, ) { /* ... */ } Button( onClick = { { (index = 0) } } ) { ... }
Actually check itrememberLazyListState
The source code, you can see that the implementation is very simple:
@Composable fun rememberLazyListState( initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemScrollOffset: Int = 0 ): LazyListState { return rememberSaveable(saver = ) { LazyListState( initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset ) } }
Use state containers as trusted sources
When a composable item contains complex interface logic involving the state of multiple interface elements, the corresponding transaction should be delegated to the state container. This makes it easier to test the logic individually and also reduces the complexity of composable terms. This method supports the principle of separating concerns: the combination items are responsible for issuing interface elements, and the state container contains interface logic and states of interface elements.
@Composable fun MyApp() { MyTheme { val myAppState = rememberMyAppState() Scaffold( scaffoldState = , bottomBar = { if () { BottomBar( tabs = , navigateToRoute = { (it) } ) } } ) { NavHost(navController = , "initial") { /* ... */ } } } }
rememberMyAppState
Code:
class MyAppState( val scaffoldState: ScaffoldState, val navController: NavHostController, private val resources: Resources, /* ... */ ) { val bottomBarTabs = /* State */ val shouldShowBottomBar: Boolean get() = /* ... */ fun navigateToBottomBarRoute(route: String) { /* ... */ } fun showSnackbar(message: String) { /* ... */ } } @Composable fun rememberMyAppState( scaffoldState: ScaffoldState = rememberScaffoldState(), navController: NavHostController = rememberNavController(), resources: Resources = , /* ... */ ) = remember(scaffoldState, navController, resources, /* ... */) { MyAppState(scaffoldState, navController, resources, /* ... */) }
In fact, it is to encapsulate another layer and handle the logic of the user. The encapsulated part is called a state container, which is used to manage the logic and state of Composable.
Use ViewModel as a trusted source
A special state container type that provides access to business logic and screen or interface state.
The life cycle of the ViewModel is longer than that of the Composable, so long-term references to states bound to the combined life cycle should not be retained. Otherwise, memory leaks may occur. Screen-level Composables are recommended to use ViewModel to provide access to business logic and serve as a trusted source of their interface state. To understand why ViewModel works for this situation, seeViewModel and state containerpart.
This article ends here, please give me some encouragement, you can also bookmark this article for emergencies.
refer to
Status and Jetpack Compose
This is the end of this article about the Jetpack Compose status. For more information about Jetpack Compose status, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!