Я хотел бы наблюдать, как использование staticCompositionLocalOf приводит к «перекомпоновке всего лямбда-контента CompositionLocalProvider» согласно документации androids.compose.runtime:
В отличие от
compositionLocalOf, чтениеstaticCompositionLocalOfне отслеживается композитором, и изменение значения, указанного в вызовеCompositionLocalProvider, приведет к перекомпоновке всего содержимого, а не только тех мест, где в композиции используется локальное значение.
Запустив прилагаемый код, я ожидал, что составной элемент Text как в setContent, так и в составном элементе Node, а также сам составной элемент Node пересоставятся, когда я нажму на Button. Однако инспектор слоев Android Studio показал, что были перекомпонованы только Leaf и Text, которые можно компоновать в Leaf. Как видно на прилагаемых скриншотах, изменение staticCompositionLocalOf во второй строке кода на compositionLocalOf привело к тем же рекомпозициям. Я также попробовал counter = mutableStateOf(Counter()) с соответствующим образом обновленным типом LocalCounter, без каких-либо различий в результатах.
data class Counter (var count: MutableState<Int> = mutableStateOf(0))
val LocalCounter = staticCompositionLocalOf<Counter> {
error("CompositionLocal LocalCounter not provided")
}
class MainActivity : ComponentActivity() {
private var counter = Counter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CompositionLocalProvider(LocalCounter provides counter) {
Column {
Text("setContent ${LocalCounter}")
Node()
Button(content = { Text("+1") },
onClick = { counter.count.value += 1 }
)
}
}
}
}
}
@Composable
fun Node() {
Text("Node")
Leaf()
}
@Composable
fun Leaf() {
val counter = LocalCounter.current
Text("Leaf count: ${counter.count.value}")
}
Я запустил код, используя Android Studio Jellyfish Patch 1 с эмулятором Pixel 8 API 34, Android Studio Koala Canary 2 с эмулятором Pixel 8 API VanillaIceCream и физический Pixel 4a с API 33. Все с Kotlin 1.9.24 и все с теми же рекомпозициями. .
Связанные вопросы о stackoverflow и Интернете в целом, в основном задаются об использовании CompositionLocal, например Как и когда именно CompositionLocal устанавливает значения неявно? или https://stackoverflow.com/a/77496284, или о концептуальной разнице между staticCompositionLocalOf и compositionLocalOf, которую объясняет официальная документация. Я не смог найти ни одного примера кода, показывающего, как можно показать разницу между этими двумя.
Любой пример, демонстрирующий рекомпозицию составных элементов, которая не использует предоставленное значение, а исключительно благодаря использованию staticCompositionLocalOf, будет очень признателен!
Обновлен код на основе ответа Яна Итора:
В одной из моих первоначальных неудачных попыток вместо наблюдения за предоставленным значением типа Counter я пытался наблюдать за предоставленным значением типа MutableState<Counter> и обновлять Counter в изменяемом файле value при нажатии Button. Вот различия с неизменяемым свойством в классе данных:
# line 1:
data class Counter(var count: Int = 0)
# line 2:
val LocalCounter = staticCompositionLocalOf<MutableState<Counter>> {
# line 6:
private var counter = mutableStateOf(Counter())
# line 18:
onClick = { counter.value = Counter(counter.value.count + 1) }
# line 35:
Text("Leaf count: ${counter.value.count}")
Полученные рекомпозиции были такими же, как и исходный код. Насколько я понимаю, хотя counter.value был обновлен, counter сам по себе нет - значение CompositionLocal по-прежнему содержит тот же указатель на тот же блок, экземпляр Counter в поле изменился, но поле и указатель на него по-прежнему остаются одинаковый. Таким образом, CompositionLocal не обнаружил никаких изменений.
Чтобы увидеть эффект staticCompositionLocalOf, необходимо изменить значение внутри самого поля CompositionLocal. Вот обновленный код:
data class Counter(var count: Int = 0)
val LocalCounter = staticCompositionLocalOf<Counter> {
error("CompositionLocal LocalCounter not provided")
}
class MainActivity : ComponentActivity() {
private var counter = mutableStateOf(Counter())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CompositionLocalProvider(LocalCounter provides counter.value) {
Column {
Text("setContent ${LocalCounter}")
Node()
Button(content = { Text("+1") },
onClick = { counter.value = Counter(counter.value.count + 1) }
)
}
}
}
}
}
@Composable
fun Node() {
Text("Node")
Leaf()
}
@Composable
fun Leaf() {
val counter = LocalCounter.current
Text("Leaf count: ${counter.count}")
}
(Использование делегирования свойств с by в строке 6 избавляет от необходимости ссылаться на .value в строках 13 и 18 для более четкого синтаксиса, но я хотел уточнить, какие типы используются.)
Чтобы уточнить официальную документацию, мое резюме таково: если наблюдаемое изменяемое состояние является членом класса, но экземпляр класса, предоставленный самим CompositionLocal, не меняется часто, использование staticCompositionLocalOf предотвращает отслеживание предоставленного (медленное изменение) пример. С другой стороны, если сам предоставленный экземпляр часто меняется, использование compositionLocalOf предотвращает рекомпозицию компонуемых объектов без учета предоставленного экземпляра. Примеры первого: оригинал Counter выше или коллекции, в которых отдельные элементы изменяемы и наблюдаемы, но сами экземпляры коллекций меняются медленно. Примеры последнего: замена целых экземпляров базовых типов, String, коллекций или пользовательских классов.
Когда вы обновляете значение CompositionLocal следующим образом: counter.count.value += 1 это на самом деле не меняет значение, указанное в вызове CompositionLocalProvider. Экземпляр Counter остается прежним, меняется только его свойство.
Когда CompositionLocal может отслеживать обновления своего значения, как в этом примере:
private val LocalCount = compositionLocalOf { 0 }
private var count by mutableIntStateOf(0)
private val LocalCountStatic = staticCompositionLocalOf { 0 }
private var countStatic by mutableIntStateOf(0)
@Composable
fun CompositionLocalTest() {
Column {
Text("Outer text")
CompositionLocalProvider(
LocalCount provides count,
LocalCountStatic provides countStatic,
) {
Column {
Text("Inner text")
Text("LocalCount ${LocalCount.current}")
Text("LocalCountStatic ${LocalCountStatic.current}")
Node()
Button(
onClick = { count = Random.nextInt(1000) }
) { Text("Change LocalCount") }
Button(
onClick = { countStatic = Random.nextInt(1000) }
) { Text("Change LocalCountStatic") }
}
}
}
}
@Composable
fun Node() {
Text("Node")
Leaf()
}
@Composable
fun Leaf() {
Text("Leaf count: ${LocalCount.current}")
Text("Leaf count static: ${LocalCountStatic.current}")
}
Нажатие на верхнюю кнопку приведет к частичной перекомпоновке:
При обновлении staticCompositionLocalOf будет перекомпонована вся content лямбда:
Спасибо! Мне удалось адаптировать ваш ответ к своему классу данных
Counterи заставить его работать. Я опубликую обновленный код как обновление моего вопроса.