简体   繁体   English

Jetpack Compose @Stable 列表<t>参数重组</t>

[英]Jetpack Compose @Stable List<T> parameter recomposition

@Composable functions are recomposed @Composable 函数被重构

  • if one the parameters is changed or如果其中一个参数被更改或
  • if one of the parameters is not @Stable/@Immutable如果其中一个参数不是@Stable/@Immutable

When passing items: List<Int> as parameter, compose always recomposes, regardless of List is immutable and cannot be changed.当传递items: List<Int>作为参数时,compose 总是重新组合,不管List是不可变的,不能改变。 ( List is interface without @Stable annotation ). List 是没有 @Stable 注释的接口)。 So any Composable function which accepts List<T> as parameter always gets recomposed, no intelligent recomposition.因此,任何接受List<T>作为参数的可组合 function 总是被重组,没有智能重组。

How to mark List<T> as stable, so compiler knows that List is immutable and function never needs recomposition because of it?如何将List<T>标记为稳定,以便编译器知道 List 是不可变的,并且 function 永远不需要重新组合?

Only way i found is wrapping like @Immutable data class ImmutableList<T>(val items: List<T>) .我发现的唯一方法是像@Immutable data class ImmutableList<T>(val items: List<T>)一样包装。 Demo (when Child1 recomposes Parent, Child2 with same List gets recomposed too) :演示(当 Child1 重组 Parent 时,具有相同 List 的 Child2 也被重组)

class TestActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBasicsTheme {
                Parent()
            }
        }
    }
}

@Composable
fun Parent() {
    Log.d("Test", "Parent Draw")
    val state = remember { mutableStateOf(false) }
    val items = remember { listOf(1, 2, 3) }

    Column {
        // click forces recomposition of Parent
        Child1(value = state.value,
            onClick = { state.value = !state.value })

        //
        Child2(items)
    }
}

@Composable
fun Child1(
    value: Boolean,
    onClick: () -> Unit
) {
    Log.d("Test", "Child1 Draw")
    Text(
        "Child1 ($value): Click to recompose Parent",
        modifier = Modifier
            .clickable { onClick() }
            .padding(8.dp)
    )
}

@Composable
fun Child2(items: List<Int>) {
    Log.d("Test", "Child2 Draw")
    Text(
        "Child 2 (${items.size})",
        modifier = Modifier
            .padding(8.dp)
    )
}

Using lambda, you can do this使用 lambda,您可以这样做

@Composable
fun Parent() {
    Log.d("Test", "Parent Draw")
    val state = remember { mutableStateOf(false) }
    val items = remember { listOf(1, 2, 3) }
    val getItems = remember(items) {
        {
            items
        }
    }

    Column {
        // click forces recomposition of Parent
        Child1(value = state.value,
            onClick = { state.value = !state.value })

        //
        Child2(items)
        Child3(getItems)
    }
}

@Composable
fun Child3(items: () -> List<Int>) {
    Log.d("Test", "Child3 Draw")
    Text(
        "Child 3 (${items().size})",
        modifier = Modifier
            .padding(8.dp)
    )
}

Another workaround is to pass around a SnapshotStateList .另一种解决方法是传递SnapshotStateList

Specifically, if you use backing values in your ViewModel as suggested in the Android codelabs, you have the same problem.具体来说,如果您按照 Android 代码实验室中的建议在ViewModel中使用支持值,则会遇到同样的问题。

private val _myList = mutableStateListOf(1, 2, 3)
val myList: List<Int> = _myList

Composables that use myList are recomposed even if _myList is unchanged.即使_myList未更改,使用myList的可组合项也会重新组合。 Opt instead to pass the mutable list directly (of course, you should treat the list as read-only still, except now the compiler won't help you).选择直接传递可变列表(当然,您应该将列表视为只读,除非现在编译器无法帮助您)。

Example with also the wrapper immutable list:带有包装器不可变列表的示例:

@Immutable
data class ImmutableList<T>(
    val items: List<T>
)

var itemsList = listOf(1, 2, 3)
var itemsImmutable = ImmutableList(itemsList)

@Composable
fun Parent() {
    Log.d("Test", "Parent Draw")
    val state = remember { mutableStateOf(false) }
    val itemsMutableState = remember { mutableStateListOf(1, 2, 3) }

    Column {
        // click forces recomposition of Parent
        Child1(state.value, onClick = { state.value = !state.value })
        ChildList(itemsListState)                   // Recomposes every time
        ChildImmutableList(itemsImmutableListState) // Does not recompose
        ChildSnapshotStateList(itemsMutableState)   // Does not recompose
    }
}

@Composable
fun Child1(
    value: Boolean,
    onClick: () -> Unit
) {
    Text(
        "Child1 ($value): Click to recompose Parent",
        modifier = Modifier
            .clickable { onClick() }
            .padding(8.dp)
    )
}

@Composable
fun ChildList(items: List<Int>) {
    Log.d("Test", "List Draw")
    Text(
        "List (${items.size})",
        modifier = Modifier
            .padding(8.dp)
    )
}

@Composable
fun ChildImmutableList(items: ImmutableList<Int>) {
    Log.d("Test", "ImmutableList Draw")
    Text(
        "ImmutableList (${items.items.size})",
        modifier = Modifier
            .padding(8.dp)
    )
}

@Composable
fun ChildSnapshotStateList(items: SnapshotStateList<Int>) {
    Log.d("Test", "SnapshotStateList Draw")
    Text(
        "SnapshotStateList (${items.size})",
        modifier = Modifier
            .padding(8.dp)
    )
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM