#android #android-layout #android-jetpack-compose
#Android #android-макет #android-реактивный ранец-compose
Вопрос:
Рассмотрим этот минимальный фрагмент кода (в Kotlin):
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import java.time.LocalDateTime
import java.util.*
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var time by remember {
mutableStateOf("time")
}
Column(modifier = Modifier.clickable { time = LocalDateTime.now().toString() }) {
Text(text = UUID.randomUUID().toString())
Text(text = time)
}
}
}
}
Рассматривая приведенный выше код с логической точки зрения, можно ожидать, что при нажатии на Column
, поскольку time
изменяется только параметр Text
, будет перерисовываться только составное значение с меньшим временем. Это потому, что перекомпозиция пропускает как можно больше.
Однако обнаруживается, что верхний Text
составной элемент также перерисовывается (отображаемый UUID продолжает меняться).
- Почему это так?
Обратите внимание, что неидемпотентность моего Column
составного элемента не должна иметь никакого отношения, если перерисовка не является тупой.
Ответ №1:
Вы можете попробовать запустить этот блок кода
@Composable
fun IdempotenceTest() {
var time by remember {
mutableStateOf("time")
}
Column(
modifier = Modifier.clickable {
time = LocalDateTime.now().toString()
}
) {
Text(text = getRandomUuid())
TestComposable(text = returnSameValue())
Text(text = time)
}
}
@Composable
fun TestComposable(text: String) {
SideEffect {
Log.d(TAG, "TestComposable composed with: $text")
}
Text(text = text)
}
private fun getRandomUuid(): String {
Log.d(TAG, "getRandomUuid: called")
return UUID.randomUUID().toString()
}
private fun returnSameValue(): String {
Log.d(TAG, "returnSameValue: called")
return "test"
}
Если вы проверите журналы, вы увидите, что каждый раз, когда изменяется состояние, компилятор пытается повторно вызвать наименьшую заключающую lamda / функцию, где считывается значение состояния. Следовательно, функция IdempotenceTest (в моем примере и setContent{} lamda в вашем) будет повторно выполнена, которая вызовет getRandomUuid
функцию returnSameValue
and и на основе возвращаемого ими значения решит, следует ли повторно составлять элементы, которые зависят от этих возвращаемых значений. Если вы хотите, чтобы вычисления не повторялись снова и снова, оберните их в remember{}
блок.
Теперь, если бы вы использовали Button
, например, вместо столбца, вы бы увидели, что была бы выполнена только одна и та же строка содержимого. Причина, по которой это происходит, заключается в том, что Column является встроенной функцией, в то время как Button сама по себе использует Surface внутри нее, которая не является встроенной. Поэтому содержимое Column{}
копируется внутри окружающего функционального блока, что приводит к тому, что все IdempotenceTest
становится недействительным для перекомпозиции.
В качестве примечания, составные функции должны быть свободны от побочных эффектов, чтобы обеспечить идемпотентность. Вы можете прочитать больше здесь.
Чтобы узнать больше о области перекомпозиции, вы можете обратиться к сообщениям в блогах здесь и здесь .
Комментарии:
1. «каждый раз, когда столбец перекомпонируется» .. в этом суть вопроса… почему весь столбец перекомпонируется в первую очередь?
2. «компилятор вызовет getRandomUuid …» почему? разве компилятор не должен вызывать только наблюдение за кодом
time
?3. Я отредактировал ответ, чтобы он содержал больше информации о областях перекомпозиции.
4. этот пост Зака действительно проясняет ситуацию .. спасибо
Ответ №2:
rememberSavable {...}
сработало для меня.
Это позволяет вам обновлять состояние непосредственно из clickable
и запускать перекомпозицию:
...
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
...
var time by rememberSaveable { mutableStateOf("time") }
Column(
modifier = Modifier.clickable {
time = LocalDateTime.now().toString()
}
) {
Text(text = UUID.randomUUID().toString())
Text(text = time)
}