State : save and update state value
also can roled as a delegated value provider through remember apis
Remember : remember last state value
if incoming new value equals to last state value, skip recompose
@Composable
private fun Temp() {
var count by remember { mutableIntStateOf(1) }
val onClick = {
count = Random.nextInt(1, 3)
}
Button(onClick = onClick) { Text(count.toString()) }
}
if new random value of count equals to remembered one, Text will skip recomposition
@Composable
private fun Temp() {
var count1 by remember { mutableIntStateOf(1) }
Button(onClick = { count1++ }, Modifier.width(200.dp).height(50.dp)) {
if (count1 % 3 == 1) {
var count2 by remember { mutableIntStateOf(1) }
Text("$count1-$count2", Modifier.clickable { count2++ })
}
}
}
remember status will be disposed, when the scope define it removed from composition
when count11, text1-1
when count13, text1-1
because when count1==2, condition is false, composition will be removed
data class User(val id: String, val name: String)
@Composable
private fun Temp() {
var id by remember { mutableStateOf("1") }
var user by remember(id) {
println("execute computation block")
mutableStateOf(User("1", "A"))
}
Text("${user.name}", Modifier.graphicsLayer { println("recompose") })
Button(onClick = {
id = UUID.short()
}, Modifier.width(100.dp).height(50.dp)) { }
Button(onClick = {
user = User("2", "B")
}, Modifier.width(100.dp).height(50.dp)) { }
}
id is a key for remember, multiple keys is also supported
when key list is same, use latest value in previous state object
when key list is different, recompute a new state object through computation block
usage of rememberSaveable
is exactly same with remember
however it will save value into Bundle in Activity/Fragment
when activity is destroyed, saved value will be restored from Bundle
as Bundle only support primitive types and Parcelable types
there are 3 ways to resolve this problem
apply kotlin parcelize plugin
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
id("org.jetbrains.kotlin.plugin.parcelize")
}
auto implement parcelable interface
@Parcelize
data class User(val id: String, val name: String) : Parcelable
@Composable
private fun Temp() {
var user by rememberSaveable { mutableStateOf(User("1", "A")) }
Text("${user.name}")
Button(onClick = {
user = User("2", "B")
}, Modifier.width(100.dp).height(50.dp)) { }
}
the principle is to convert compositive type into multiple primitive types for storage, like ObjectStream in JDK
data class User(val id: String, val name: String)
private val UserListSaver = listSaver<User, Any>(
save = { listOf(it.id, it.name) },
restore = {
val id = it[0] as String
val name = it[1] as String
User(id, name)
}
)
@Composable
private fun Temp() {
var user by rememberSaveable(stateSaver = UserListSaver) { mutableStateOf(User("1", "A")) }
Text("${user.name}")
Button(onClick = {
user = User("2", "B")
}, Modifier.width(100.dp).height(50.dp)) { }
}
similarly, MapSaver convert custom type into a map structure
private val UserMapSaver = mapSaver(
save = { mapOf("id" to it.id, "name" to it.name) },
restore = {
val id = it["id"] as String
val name = it["name"] as String
User(id, name)
}
)
create a new state depend on other states
var count1 by remember { mutableIntStateOf(0) }
var count2 by remember { mutableIntStateOf(0) }
val count by remember { derivedStateOf { count1 + count2 } }
create a new state depend on other states
but state is generated asynchronously in a coroutine scope
var foregroundColor by remember { mutableStateOf(Color.Red) }
val backgroundColor by produceState(Color.White) {
delay(1000L)
foregroundColor.let {
value = Color(it.red, it.green, it.blue, 0.1f)
}
}
equals to code below, but return value is immutable, value cannot be modified
@Composable
fun <T> rememberUpdatedState(newValue: T): State<T> = remember {
mutableStateOf(newValue)
}.apply { value = newValue }
value delegated by rememberUpdatedState cannot be modified directly
this is the difference to remember api, its value is always updated by its dependency object
@Composable
private fun Counter(foregroundColor: Color, modifier: Modifier) {
val foregroundColor by rememberUpdatedState(foregroundColor)
val backgroundColor = remember(foregroundColor) {
val origin = foregroundColor
Color(origin.red, origin.green, origin.blue, 0.2f)
}
Box(modifier = modifier.background(backgroundColor)) {
Text("1", color = foregroundColor)
}
}
@Preview
@Composable
fun Compose42() {
var foregroundColor by remember { mutableStateOf(Color.Red) }
Counter(foregroundColor, Modifier.size(100.dp).clickable { foregroundColor = Color.Blue })
}
create a coroutine scope that have a same lifecycle with composable
@Composable
private fun Counter() {
val coroutineScope = rememberCoroutineScope()
var count by remember { mutableIntStateOf(0) }
val onClick: () -> Unit = {
coroutineScope.launch {
delay(3000)
count++
}
}
Button(onClick = onClick) {
Text("$count")
}
}