SoFunction
Updated on 2025-04-09

Jetpack Compose Status Special Lecture

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.

rememberIt 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 attributevalueFixed to an empty string. We userememberOptimize it:

@Composable
fun HelloContent() {
    val inputValue = remember { mutableStateOf("") }
    Column(modifier = ()) {
        OutlinedTextField(
            value = ,
            onValueChange = {
                 = it
            },
            label = { Text("Name") }
        )
    }
}

passonValueChangeUpdate value,mutableStateOfWill create observableMutableState<T>, When value is changed, the system will reorganize all read valueComposablefunction, 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.

  • LiveDataExtended functions can be usedobserveAsState()Convert to State.
  • FlowExtended functions can be usedcollectAsState()Convert to State.
  • RxJavaExtended functions can be usedsubscribeAsState()Convert to State.

AlthoughrememberHelps you stay state after reorganization, but won't help you stay state after configuration changes. To do this, you must userememberSaveablerememberSaveableAny 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 nowrememberSaveablereplacerememberTo help us restore the interface state.

Since the saved data isBundleTherefore, 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@ParcelizeAnnotations can solve the problem.

If some reason does not work@Parcelize, you can usemapSaverCustom 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 itlistSaverand 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 aboverememberorrememberSaveStateMethod to save stateComposableFunctions, 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 stateComposableIt is often not easy to reuse and is more difficult to test.

In development of reusableComposableWhen you usually want to provide the sameComposablestateful 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 doneComposableThe 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 articleLazyColumnScroll to the specified item and place the interactions in the currentComposableProgress in.

	val listState = rememberLazyListState()
    val coroutineScope = rememberCoroutineScope()
    LazyColumn(
        state = listState,
    ) {
       /* ... */
    }
	Button(
        onClick = {
             {
                (index = 0)
            }
        }
    ) {
        ...
    }

Actually check itrememberLazyListStateThe 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") { /* ... */ }
        }
    }
}

rememberMyAppStateCode:

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!