在Android中制作有状态组件



我在应用程序中使用MVVM。当您输入查询并单击搜索按钮时,链如下:Fragment->ViewModel->Repository->API->Client。客户端是进行HTTP请求的地方。但这里有一件事,客户端需要在初始化时进行调用并从服务器获取密钥。因此,为了在第一次调用完成之前阻止任何调用,我需要能够从Fragment中观察它,以便禁用搜索按钮。由于链中的每个组件都可以与相邻组件通信,因此所有组件都应该有一个状态。

我正在考虑实现一个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超类。

如何实现此问题的解决方案?

最常见的方法是使用密封类作为状态,因此在每个状态情况下都可以根据需要使用任何参数。

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 -> { ... }
}
}
)
}
}

正如João Gouveia所提到的,我们可以使用kotlin的密封类非常容易地制作有状态组件。

但是为了使它更加有用,我们可以引入Generics!因此,我们的状态类变成了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>()
}

我写了一篇文章,在这里充分解释了这个特定的实现https://naingaungluu.medium.com/stateful-data-on-android-with-sealed-classes-and-kotlin-flow-33e2537ccf55

如果您正在使用可组合。。。您可以使用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
}

相关内容

  • 没有找到相关文章

最新更新