Все еще новичок в составлении + kotlin, поэтому у меня возникли проблемы с работой потока состояний. Могут быть некоторые основы, которые я не понимаю или упускаю из виду какую-то функцию.
Проблема: У меня есть два потока состояний в моей модели представления, и оба они вызовут рекомпозицию другого.
ViewModel:
private val _networkUiState = MutableStateFlow<NetworkUIState>(NetworkUIState.Empty)
val networkUIState: StateFlow<NetworkUIState> = _networkUiState.asStateFlow()
private val _uiState = MutableStateFlow<UIState>(UIState.Empty)
val uiState: StateFlow<UIState> = _uiState.asStateFlow()
fun someAPICall(
) {
_networkUiState.value = NetworkUIState.Loading
networkJob = CoroutineScope(Dispatchers.IO + exceptionHandler).launch {
try {
val response = repo.apiCall()
withContext(Dispatchers.Main) {
_networkUiState.value = NetworkUIState.Loaded
_uiState.value = UIState.DoSomething(response)
}
} catch (error: NetworkException) {
_networkUiState.value = NetworkUIState.NetworkError(error)
}
}
}
//exceptionHandler calls _networkUIState.value = NetworkUIState.NetworkError(some error) as well
Написать:
val networkUIState = viewModel.networkUIState.collectAsState().value
val uiState = viewModel.uiState.collectAsState().value
Column() {
//...
//UI code
when (uiState) {
is UIState.DoSomething -> {
//read uiState.response and do something
}
is UIState.DoAnotherThing -> {
//read response and do something else
}
}
when (networkUIState) {
is NetworkUIState.Error -> {
//display network error
}
is NetworkUIState.Loading -> {
//display loading dialog
}
else -> {}
}
}
Что происходит:
1-й раз вызов API:
2-й раз вызов API:
Я понимаю, что это из-за перекомпоновки NetworkUiState перед UIState, но UIState по-прежнему имеет старое значение. Мой вопрос в том, как я могу избежать этого, когда мне абсолютно необходимо разделить эти 2 состояния, по крайней мере, в модели представления?
UiState.DoSoemthing, прямо сейчас — это просто Log.d, чтобы посмотреть, что происходит. Рекомпозиция нормальная, ответ Thracian - это то, о чем я думал изначально, но я ищу что-то лучшее.
Но в чем проблема, если она инициируется изменением другого состояния? Вот как все работает в Compose. Вы должны спроектировать его так, чтобы он не имел значения (без побочных эффектов).
Я знаю, как работает compose, как я сказал в своем посте. Я просто спрашиваю, есть ли лучшее решение проблемы (у Thracian была правильная идея, что я изначально и думал сделать). Есть ли что-то, что может объединить два потока состояний в компоновке, но при этом сохранить их отдельными в модели представления?
Что мне не ясно, так это то, что вы на самом деле делаете, что было бы проблемой, если бы это вызывалось неоднократно из-за рекомпозиций. Вы не должны делать такие вещи в первую очередь. Не понимая этого, трудно подсказать, как ее решить. Прямо сейчас это вызов журнала, который не вызовет проблем. Если есть побочный эффект, который должен возникать только один раз при изменении определенного состояния, его следует обернуть в LaunchedEffect, если вы не можете полностью удалить его из композиции.
Никаких побочных эффектов не происходит, опять же, я не знаю, как объяснить вам дальше, потому что ответ Thracian - это решение, и я ищу что-то похожее на это, но лучше (о чем я говорил много раз). Вы в основном говорите мне, не используйте 2 потока состояний, это то, что вы говорите?
Нет, не должно быть проблемой иметь даже 100 потоков состояний. Составляющая документация говорит, что нельзя создавать побочные эффекты, потому что ваша составная функция может вызываться много раз без изменения состояния. Если никаких побочных эффектов не происходит, я не понимаю, почему ваш код вызывается несколько раз при изменении другого несвязанного StateFlow.
Самое простое, что вы можете сделать, это предоставить состояние Idle или DoNothing для вашего UiState, которое и установить в это состояние, когда вы закончите выполнение события или когда вы начнете новый вызов API. Это шаблон, который я часто использую, я также использую его, если изначально не должно быть загрузки в пользовательском интерфейсе. uiState начинается с Idle, когда пользователь щелкает, чтобы получить из API, я устанавливаю «Загрузка», а затем «Ошибка» или «Успех» в зависимости от результата выборки данных.
Я также использую этот шаблон с жестами, чтобы не выходить из состояния «Вверх», когда пользователь поднимает пальцы, чтобы не начинать рисовать из состояния «Вверх», как в этом ответе
да, я думал об этом изначально, но решил спросить здесь, есть ли лучшие решения. Потому что всегда возвращаться к пустому (или бездействующему) состоянию, кажется, очень легко ввести ошибки. Особенно работая в командной среде, другие разработчики могут не помнить об этом.
Через несколько дней кажется, что ни у кого нет лучшего решения, чем выполнить сброс / очистку. Таким образом, выбрав это в качестве ответа.
Что вы делаете в своей композиции, когда получаете UiState.DoSomething? Вы нарушаете главное правило отсутствия побочных эффектов? Это единственная причина, по которой я могу думать, что для них будет проблемой вызвать рекомпозицию друг друга. Ваша композиция должна быть спроектирована так, как будто вы можете многократно перекомпоновывать ее без изменения состояния, и это не вызовет никаких проблем.