Android составляет несколько рекомпозиций потоков состояний

Все еще новичок в составлении + 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:

  1. NetworkUIState запускает загрузку (загрузка дисплея)
  2. Триггеры NetworkUIState загружены (скрывает загрузку)
  3. uiState запускает DoSomething с ответными данными

2-й раз вызов API:

  1. NetworkUIState запускает загрузку (загрузка дисплея)
  2. uiState запускает DoSomething с данными ответа (из последнего вызова)
  3. Триггеры NetworkUIState загружены (скрывает загрузку)
  4. uiState запускает DoSomething с ответными данными (новые данные)

Я понимаю, что это из-за перекомпоновки NetworkUiState перед UIState, но UIState по-прежнему имеет старое значение. Мой вопрос в том, как я могу избежать этого, когда мне абсолютно необходимо разделить эти 2 состояния, по крайней мере, в модели представления?

Что вы делаете в своей композиции, когда получаете UiState.DoSomething? Вы нарушаете главное правило отсутствия побочных эффектов? Это единственная причина, по которой я могу думать, что для них будет проблемой вызвать рекомпозицию друг друга. Ваша композиция должна быть спроектирована так, как будто вы можете многократно перекомпоновывать ее без изменения состояния, и это не вызовет никаких проблем.

Tenfour04 27.11.2022 14:54

UiState.DoSoemthing, прямо сейчас — это просто Log.d, чтобы посмотреть, что происходит. Рекомпозиция нормальная, ответ Thracian - это то, о чем я думал изначально, но я ищу что-то лучшее.

Slodin 27.11.2022 23:08

Но в чем проблема, если она инициируется изменением другого состояния? Вот как все работает в Compose. Вы должны спроектировать его так, чтобы он не имел значения (без побочных эффектов).

Tenfour04 28.11.2022 02:01

Я знаю, как работает compose, как я сказал в своем посте. Я просто спрашиваю, есть ли лучшее решение проблемы (у Thracian была правильная идея, что я изначально и думал сделать). Есть ли что-то, что может объединить два потока состояний в компоновке, но при этом сохранить их отдельными в модели представления?

Slodin 28.11.2022 10:39

Что мне не ясно, так это то, что вы на самом деле делаете, что было бы проблемой, если бы это вызывалось неоднократно из-за рекомпозиций. Вы не должны делать такие вещи в первую очередь. Не понимая этого, трудно подсказать, как ее решить. Прямо сейчас это вызов журнала, который не вызовет проблем. Если есть побочный эффект, который должен возникать только один раз при изменении определенного состояния, его следует обернуть в LaunchedEffect, если вы не можете полностью удалить его из композиции.

Tenfour04 28.11.2022 13:47

Никаких побочных эффектов не происходит, опять же, я не знаю, как объяснить вам дальше, потому что ответ Thracian - это решение, и я ищу что-то похожее на это, но лучше (о чем я говорил много раз). Вы в основном говорите мне, не используйте 2 потока состояний, это то, что вы говорите?

Slodin 29.11.2022 08:46

Нет, не должно быть проблемой иметь даже 100 потоков состояний. Составляющая документация говорит, что нельзя создавать побочные эффекты, потому что ваша составная функция может вызываться много раз без изменения состояния. Если никаких побочных эффектов не происходит, я не понимаю, почему ваш код вызывается несколько раз при изменении другого несвязанного StateFlow.

Tenfour04 29.11.2022 13:36
0
7
410
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Самое простое, что вы можете сделать, это предоставить состояние Idle или DoNothing для вашего UiState, которое и установить в это состояние, когда вы закончите выполнение события или когда вы начнете новый вызов API. Это шаблон, который я часто использую, я также использую его, если изначально не должно быть загрузки в пользовательском интерфейсе. uiState начинается с Idle, когда пользователь щелкает, чтобы получить из API, я устанавливаю «Загрузка», а затем «Ошибка» или «Успех» в зависимости от результата выборки данных.

Я также использую этот шаблон с жестами, чтобы не выходить из состояния «Вверх», когда пользователь поднимает пальцы, чтобы не начинать рисовать из состояния «Вверх», как в этом ответе

да, я думал об этом изначально, но решил спросить здесь, есть ли лучшие решения. Потому что всегда возвращаться к пустому (или бездействующему) состоянию, кажется, очень легко ввести ошибки. Особенно работая в командной среде, другие разработчики могут не помнить об этом.

Slodin 27.11.2022 11:16

Через несколько дней кажется, что ни у кого нет лучшего решения, чем выполнить сброс / очистку. Таким образом, выбрав это в качестве ответа.

Slodin 13.12.2022 23:58

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