Создание компонентов с состоянием в Android - PullRequest
1 голос
/ 21 апреля 2020

Я использую MVVM в своем приложении. Когда вы вводите запрос и нажимаете кнопку поиска, цепочка выглядит следующим образом: Fragment -> ViewModel -> Repository -> API -> Client. Клиент, где HTTP-запросы сделаны. Но здесь есть одна вещь: клиент должен сделать звонок и получить ключ от сервера при инициализации. Поэтому, чтобы предотвратить любой вызов до того, как этот первый вызов завершится, мне нужно иметь возможность наблюдать его из фрагмента, чтобы я мог отключить кнопку поиска. Поскольку каждый компонент в цепочке может взаимодействовать со смежными компонентами, все компоненты должны иметь состояние.

Я думаю реализовать класс StatefulComponent и заставить все компоненты расширять его:

open class StatefulComponent protected constructor() {
    enum class State {
        CREATED, LOADING, LOADED, FAILED
    }

    private val currentState = MutableLiveData(State.CREATED)

    fun setState(newState: State) {
        currentState.value = newState
    }

    val state: LiveData<State> = currentState

    val isLoaded: Boolean = currentState.value == State.LOADED

    val isFailed: Boolean = currentState.value == State.FAILED

    val isCompleted: Boolean = isLoaded || isFailed
}

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

Как я могу реализовать решение для этой проблемы?

1 Ответ

0 голосов
/ 21 апреля 2020

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

sealed class MyState {
   object Loading : MyState()
   data class Loaded(data: Data) : MyState()
   data class Failed(message: String) : MyState()
}

В вашей модели представления у вас будет только 1 жилатата

class MyViewModel : ViewModel() {
    private val _state = MutableLiveData<MyState>()
    val state: LiveData<MyState> = _state

    fun load() {
       _state.postCall(Loading)
       repo.loadSomeData(onData = { data ->
           _state.postCall(Loaded(data))
       }, onError = { error -> _state.postCall(Failed(error.message)) })
    }

    // coroutines approach
    suspend fun loadSuspend() {
      _state.postCall(Loading)
      try {
        _state.postCall(Loaded(repo.loadSomeDataSupend()))
      } catch(e: Exception) {
        _state.postCall(Failed(e.message))
      }
    }
}

А на фрагменте просто соблюдайте состояние

class MyFragment : Fragment() {
   ...
   onViewCreated() {
     viewModel.state.observer(Observer {
         when (state) {
          // auto casts to each state
          Loading -> { button.isEnabled = false }
          is Loaded -> { ... }
          is Failed -> { ... }
         }
       }
     )
   }
}
...