Как наблюдать эффект staticCompositionLocalOf()?

Я хотел бы наблюдать, как использование 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, коллекций или пользовательских классов.

1
0
135
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Когда вы обновляете значение 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 и заставить его работать. Я опубликую обновленный код как обновление моего вопроса.

Andy Hill 21.05.2024 14:14

Другие вопросы по теме

Похожие вопросы

Возникла проблема при оценке проекта «:react-native-firebase_app». Слишком поздно задавать пространство имен. Он уже прочитан для настройки этого проекта
О сборе значений из хранилища данных
Android-приложение Unity аварийно завершает работу при установке из Google Play Store
Не удается поделиться контентом в WhatsApp, когда включен AppLock
Столкновение с проблемой эмулятора в Android: «Ошибка: устройство уже активируется»
Как перейти с GoogleSignInClient на диспетчер учетных данных для аутентификации пользователей в Android?
Android-приложение Qt 6.7.1 не запускается с ошибкой: java.lang.RuntimeException: невозможно создать экземпляр приложения
В библиотеках каталога версий я получаю ложные ошибки, вы можете вызвать метод from только один раз
Круглый индикатор выполнения в JetPack
Flutter run не работает для проекта Firebase Android. Ошибка: двоичная версия метаданных — 1.9.0, ожидаемая версия — 1.7.1