Compose

Jetpack Compose Beta 版现已发布!

https://developer.android.google.cn/jetpack/compose/documentation

https://github.com/android/compose-samples

深入详解 Jetpack Compose | 实现原理

jetpack compse原理解析

View 嵌套太深会卡?来用 Jetpack Compose,随便套

State In Compose

@Composable
fun HelloScreen() {
    var name by rememberSaveable { mutableStateOf("") }

    HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.h5
        )
        OutlinedTextField(
            value = name,
            onValueChange = { onNameChange(it) },
            label = { Text("Name") }
        )
    }
}

Compose和DataBinding State对应

graph TB

subgraph DataBinding
ViewAttribute-->|event|BackData
BackData-->|state|ViewAttribute
end

subgraph Compose
HelloContent-->|event|HelloScreen
HelloScreen("HelloScreen(stateful)")-->|state|HelloContent("HelloContent(stateless)")
end

Key Point: When hoisting state, there are three rules to help you figure out where state should go:

  1. State should be hoisted to at least the lowest common parent of all composables that use the state (read).
  2. State should be hoisted to at least the highest level it may be changed (write).
  3. If two states change in response to the same events they should be hoisted together.

You can hoist state higher than these rules require, but underhoisting state will make it difficult or impossible to follow unidirectional data flow.

interface MutableState<T> : State<T> {
   override var value: T
}

Any changes to value will schedule recomposition of any composable functions that read value. Compose will automatically recompose from reading State objects.

If you use another observable type such as LiveData in Compose, you should convert it to State<T> before reading it in a composable using a composable extension function like LiveData<T>.observeAsState().

State should be modified by events in a composable. If you modify state when running a composable instead of in an event, this is a side-effect of the composable, which should be avoided. For more information about side-effects in Jetpack Compose, see Thinking in Compose.

A Composition can only be produced by an initial composition and updated by recomposition. The only way to modify a Composition is through recomposition.

If during a recomposition a composable calls different composables than it did during the previous composition, Compose will identify which composables were called or not called and for the composables that were called in both compositions, Compose will avoid recomposing them if their inputs haven’t changed.

Key Point: Use the key composable to help Compose identify composable instances in Composition. It’s important when multiple composables are called from the same call site and contain side-effects or internal state.

Skipping if the inputs haven’t changed

If a composable is already in the Composition, it can skip recomposition if all the inputs are stable and haven’t changed.

A stable type must comply with the following contract:

  • The result of equals for two instances will forever be the same for the same two instances.
  • If a public property of the type changes, Composition will be notified.
  • All public property types are also stable.

There are some important common types that fall into this contract that the compose compiler will treat as @Stable, even though they are not explicitly marked as @Stable.

  • All primitive value types: Boolean, Int, Long, Float, Char, etc.
  • Strings
  • All Function types (lambdas)

All of these types are able to follow the contract of @Stable because they are immutable. Since immutable types never change, they never have to notify Composition of the change, so it is much easier to follow this contract.

Note: All deeply immutable types can safely be considered stable types.

One notable type that is stable but is mutable is Compose’s MutableState type. If a value is held in a MutableState, the state object overall is considered to be stable as Compose will be notified of any changes to the .value property of State.

When all types passed as parameters to a composable are stable, the parameter values are compared for equality based on the composable position in the UI tree. Recomposition is skipped if all the values are unchanged since the previous call.

Key Point: Compose skips the recomposition of a composable if all the inputs are stable and haven’t changed. The comparison uses the equals method.

graph LR
Coroutine-->|InsideComposable|LaunchedEffect
Coroutine-->|OutsideComposable|rememberCoroutineScope

ViewModels in Compose

If you use the Architecture Components ViewModel library, you can access a ViewModel from any composable by calling the [viewModel()](https://developer.android.google.cn/reference/kotlin/androidx/compose/ui/viewinterop/package-summary#viewModel(kotlin.String, androidx.lifecycle.ViewModelProvider.Factory)) function, as explained in the Compose integration with common libraries documentation.

When adopting Compose, be careful about using the same ViewModel type in different composables as ViewModel elements follow View-lifecycle scopes. The scope will be either the host activity, fragment or the navigation graph if the Navigation library is used.

Note: The same instance of a ViewModel type will be used in all composables unless the composable is a destination of the navigation graph or different activity or fragment instances.

Unidirectional data flow in Jetpack Compose

Key Points:

mutableStateOf(value) creates a MutableState, which is an observable type in Compose. Any changes to its value will schedule recomposition of any composable functions that read that value.

remember stores objects in the composition, and forgets the object when the composable that called remember is removed from the composition.

rememberSaveable retains the state across configuration changes by saving it in a Bundle.

Events in your architecture

Prefer passing immutable values for state and event handler lambdas. This approach has the following benefits:

  • You improve reusability.
  • You ensure that your UI doesn’t change the value of the state directly.
  • You avoid concurrency issues because you make sure that the state isn’t mutated from another thread.
  • Often, you reduce code complexity.