Как правильно подойти к вопросу: compose: Откуда берется рассинхрон в mutableState при его изменении в LaunchedEffect?

Ссылка скопирована
1 ответ

Сейчас ситуация такая: друзья, только изучаю compose, и столкнулся с проблемой:

Есть объект
SomeState(val result : SomeObject? = null)

По вводным: есть ViewModel, в которой:

val state = mutableStateOf<SomeState>(SomeState(null)) fun load() { state.value = SomeState(null) /// some operation state.value = SomeState(someNotNullResult) }

По вводным: и есть Composable функция

val vm = hiltViewModel<SomeViewModel>(key = "123") LaunchedEffect(Unit) { vm.load() } if(vm.state.value.result != null) { Show(vm.state.value.result) }

Сейчас ситуация такая: все это у меня показывается в модальном диалоге, то есть активити / фрегмент не меняется, соответсвенно ViewModel переиспользуется.

По вводным: наблюдаю следующее поведение.

По вводным: открываю модальное окно первый раз
По вводным: все работает нормально, метод load в LaunchedEffect загружает объект, и потом его успешно отображает.

По вводным: но при втором открытии модального окна происходит следующее (судя по дебагу):

Сейчас ситуация такая: изначально vm.state.value.result не равно NULL - так как он подгружен при прошлом открытии.
Это понятно.

По вводным: далее метод load в LaunchedEffect вызывает state.value = SomeState(null), то есть vm.state.value.result теперь должно быть null

По вводным: но в Composable функции условие vm.state.value.result!= null все равно проходит, ну и приложение падает, так как в этот момент vm.state.value.result в действительности равно null

Как такое может быть?

Нужно решить такую задачу?

Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.

Заказать помощь
Лучший ответ
1
Кирилл JS Ответ

В Jetpack Compose у вас не “рассинхрон” mutableState, а нормальное поведение переиспользуемой ViewModel. Вы открываете модальное окно повторно, но ViewModel с ключом 123 остаётся той же. Поэтому в момент первой композиции состояние ещё содержит прошлый result, UI успевает увидеть старое значение, а уже потом LaunchedEffect вызывает load() и сбрасывает его в null.

LaunchedEffect(Unit) запускается после входа composable в композицию, а проверка if(vm.state.value.result != null) происходит во время композиции. Поэтому порядок такой: сначала рисуется старый result, потом стартует эффект, потом state становится null, потом приходит новый result.

Первый вариант — очищать состояние до открытия модального окна, а не внутри LaunchedEffect. Например, при нажатии на кнопку:

fun openDialog() {
    viewModel.reset()
    showDialog = true
}

fun openDialog() { viewModel.reset() showDialog = true }

Второй вариант — сделать состояние с явным статусом загрузки:

data class SomeState(
    val isLoading: Boolean = false,
    val result: SomeObject? = null
)
 
fun load() {
    state.value = SomeState(isLoading = true)
    viewModelScope.launch {
        val result = repository.load()
        state.value = SomeState(result = result)
    }
}

data class SomeState( val isLoading: Boolean = false, val result: SomeObject? = null ) fun load() { state.value = SomeState(isLoading = true) viewModelScope.launch { val result = repository.load() state.value = SomeState(result = result) } }

В UI тогда не нужно угадывать по null:

val state by vm.state
 
when {
    state.isLoading -&gt; Loader()
    state.result != null -&gt; Show(state.result)
}

val state by vm.state when { state.isLoading -&gt; Loader() state.result != null -&gt; Show(state.result) }

Третий вариант — использовать уникальный key для ViewModel/диалога, если каждый запуск должен быть полностью новым экземпляром. Но это не всегда правильно: часто ViewModel специально сохраняют, чтобы не терять данные при пересоздании UI.

Ещё лучше заменить mutableStateOf в ViewModel на StateFlow, если состояние асинхронное:

private val _state = MutableStateFlow(SomeState())
val state = _state.asStateFlow()

private val _state = MutableStateFlow(SomeState()) val state = _state.asStateFlow()

Итог: старое значение видно потому, что ViewModel переиспользуется, а LaunchedEffect не выполняется до первой проверки UI. Сбрасывайте состояние до открытия, используйте loading-состояние или создавайте новый key, если вам нужен полностью новый цикл данных.

Другие ответы (0)

Пока нет других ответов. Будьте первым, кто поможет автору.

Ответить на вопрос

комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Вам также может быть интересно