简体   繁体   English

在 Android 中制作有状态组件

[英]Making stateful components in Android

I am using MVVM in my app.我在我的应用程序中使用 MVVM。 When you enter a query and click search button, the chain is as follows: Fragment -> ViewModel -> Repository -> API -> Client.当您输入查询并单击搜索按钮时,链如下:Fragment -> ViewModel -> Repository -> API -> Client。 The client is where HTTP requests are made.客户端是发出 HTTP 请求的地方。 But there is one thing here, the client needs to make a call and get a key from the server at initialization.但是这里有一点,客户端需要在初始化时调用并从服务器获取密钥。 Therefore, to prevent any call before it this first call completes, I need to be able to observe it from Fragment so that I can disable search button.因此,为了防止在第一次调用完成之前进行任何调用,我需要能够从 Fragment 中观察它,以便我可以禁用搜索按钮。 Since each component in the chain can communicate with adjacent components, all components should have a state.由于链中的每个组件都可以与相邻组件通信,因此所有组件都应该有一个 state。

I am thinking to implement a StatefulComponent class and make all components to extend it:我正在考虑实施一个StatefulComponent class 并使所有组件扩展它:

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
}

The idea is that each component observers the next one and updates itself accordingly.这个想法是每个组件观察下一个组件并相应地更新自己。 However, this is not possible for ViewModel since it is already extending ViewModel super class.但是,这对于 ViewModel 是不可能的,因为它已经扩展了ViewModel super class。

How can I implement a solution for this problem?我该如何实施这个问题的解决方案?

The most common approach is to use sealed class as your state, so you have any paramaters as you want on each state case.最常见的方法是使用密封的 class 作为您的 state,因此您可以在每个 state 案例上随意设置任何参数。

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

On your viewmodel you will have only 1 livedata在您的视图模型上,您将只有 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))
      }
    }
}

And on the fragment, just observe the state而在片段上,只需观察 state

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

As João Gouveia mentioned, we can make stateful components quite easily using kotlin's sealed classes.正如João Gouveia提到的,我们可以使用 kotlin 的密封类非常轻松地制作有状态组件。

But to make it further more useful, we can introduce Generics, So, our state class becomes StatefulData<T> which you can use pretty much anywhere (LiveData, Flows, or even in Callbacks).但是为了让它更有用,我们可以引入 Generics,因此,我们的 state class 变成了StatefulData<T> ,您几乎可以在任何地方使用它(LiveData、Flows,甚至在回调中)。

sealed class StatefulData<out T : Any> {
    data class Success<T : Any>(val result : T) : StatefulData<T>()
    data class Error(val msg : String) : StatefulData<Nothing>()
    object Loading : StatefulData<Nothing>()
}

I've wrote an article fully explaining this particular implementation here https://naingaungluu.medium.com/stateful-data-on-android-with-sealed-classes-and-kotlin-flow-33e2537ccf55我在这里写了一篇文章来全面解释这个特定的实现https://naingaungluu.medium.com/stateful-data-on-android-with-sealed-classes-and-kotlin-flow-33e2537ccf55

If you are using the composable... You can use produce state如果您正在使用可组合...您可以使用produce state

@Composable
fun PokemonDetailScreen(
    viewModel: PokemonDetailVm = hiltViewModel()
) {
    /**
     * This takes a initial state and with that we get a coroutine scope where we can call a API and assign the data into the value
     */
    val pokemonInfo = produceState<Resource<Pokemon>>(initialValue = Resource.Loading()) {
        value = viewModel.getPokemonInfo(pokemonName)
    }.value

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM