Gio*_*ano 12 android state mvvm viewmodel
我正在开发一个Android应用程序,使用LiveData上的MVVM模式(可能是Transformations)和View和ViewModel之间的DataBinding.由于应用程序"正在增长",现在ViewModels包含大量数据,后者中的大多数都保存为LiveData以使视图订阅它们(当然,UI需要这些数据,不管是双向绑定如何每个EditTexts或单向绑定).我听到(和Google搜索)关于在ViewModel中保存代表UI状态的数据.但是,我发现的结果只是简单而通用.我想知道是否有人提示或者可以就此案例分享一些关于最佳实践的知识.简单来说,考虑到LiveData和DataBinding可用,在ViewModel中存储UI(View)状态的最佳方法是什么?提前感谢您的回答!
Eti*_*art 25
我在工作中遇到同样的问题,可以分享对我们有用的东西.我们在Kotlin开发100%,所以下面的代码示例也是如此.
为了防止ViewModel大量LiveData属性变得臃肿,请ViewState为视图(Activity或Fragment)公开单个以进行观察.它可能包含先前由多个LiveData以及视图可能需要正确显示的任何其他信息公开的数据:
data class LoginViewState (
val user: String = "",
val password: String = "",
val checking: Boolean = false
)
Run Code Online (Sandbox Code Playgroud)
请注意,我正在使用具有状态的不可变属性的Data类,并且故意不使用任何Android资源.这不是MVVM特有的,但是不可变的视图状态可以防止UI不一致和线程问题.
在ViewModel创建一个LiveData属性以暴露状态并初始化它:
class LoginViewModel : ViewModel() {
private val _state = MutableLiveData<LoginViewState>()
val state : LiveData<LoginViewState> get() = _state
init {
_state.value = LoginViewState()
}
}
Run Code Online (Sandbox Code Playgroud)
然后发出一个新状态,copy从以下任何地方使用Kotlin的Data类提供的函数ViewModel:
_state.value = _state.value!!.copy(checking = true)
Run Code Online (Sandbox Code Playgroud)
在视图中,像观察其他任何一样观察状态LiveData并相应地更新布局.在View层中,您可以将州的属性转换为实际的视图可见性,并使用具有完全访问权限的资源Context:
viewModel.state.observe(this, Observer {
it?.let {
userTextView.text = it.user
passwordTextView.text = it.password
checkingImageView.setImageResource(
if (it.checking) R.drawable.checking else R.drawable.waiting
)
}
})
Run Code Online (Sandbox Code Playgroud)
由于您之前可能已经从数据库或网络调用中公开了结果和数据ViewModel,因此您可以使用a MediatorLiveData将这些结果和数据混合为单个状态:
private val _state = MediatorLiveData<LoginViewState>()
val state : LiveData<LoginViewState> get() = _state
_state.addSource(databaseUserLiveData, { name ->
_state.value = _state.value!!.copy(user = name)
})
...
Run Code Online (Sandbox Code Playgroud)
由于统一的,不可变的ViewState本质上打破了数据绑定库的通知机制,因此我们使用一个BindingState扩展的mutable BaseObservable来有选择地通知更改的布局.它提供了一个refresh接收相应的函数ViewState:
更新:删除了if语句检查更改的值,因为数据绑定库已经只处理实际更改的值.感谢@CarsonHolzheimer
class LoginBindingState : BaseObservable() {
@get:Bindable
var user = ""
private set(value) {
field = value
notifyPropertyChanged(BR.user)
}
@get:Bindable
var password = ""
private set(value) {
field = value
notifyPropertyChanged(BR.password)
}
@get:Bindable
var checkingResId = R.drawable.waiting
private set(value) {
field = value
notifyPropertyChanged(BR.checking)
}
fun refresh(state: AngryCatViewState) {
user = state.user
password = state.password
checking = if (it.checking) R.drawable.checking else R.drawable.waiting
}
}
Run Code Online (Sandbox Code Playgroud)
为创建在观察视图属性BindingState和调用refresh来自Observer:
private val state = LoginBindingState()
...
viewModel.state.observe(this, Observer { it?.let { state.refresh(it) } })
binding.state = state
Run Code Online (Sandbox Code Playgroud)
然后,将状态用作布局中的任何其他变量:
<layout ...>
<data>
<variable name="state" type=".LoginBindingState"/>
</data>
...
<TextView
...
android:text="@{state.user}"/>
<TextView
...
android:text="@{state.password}"/>
<ImageView
...
app:imageResource="@{state.checkingResId}"/>
...
</layout>
Run Code Online (Sandbox Code Playgroud)
一些样板文件肯定会受益于扩展函数和委托属性,例如更新ViewState和通知更改BindingState.
如果您想了解有关使用"干净"架构的Architecture Components处理状态和状态的更多信息,您可以在GitHub上查看Eiffel.
这是我专为处理不可变的视图状态和数据绑定创建一个图书馆ViewModel和LiveData以及采用Android系统的运营和业务用例一起胶合它.文档比我在这里提供的内容更深入.
2019 年12 月 18 日更新:Android 单向数据流与 LiveData — 2.0
我使用Kotlin和LiveData设计了一种基于单向数据流的模式。
查看完整的Medium帖子或YouTube演讲以获得深入的解释。
中 -带有 LiveData 的 Android 单向数据流
YouTube -单向数据流 - Adam Hurwitz - Medellín Android Meetup
视图状态.kt
// Immutable ViewState attributes.
data class ViewState(val contentList:LiveData<PagedList<Content>>, ...)
// View sends to business logic.
sealed class ViewEvent {
data class ScreenLoad(...) : ViewEvent()
...
}
// Business logic sends to UI.
sealed class ViewEffect {
class UpdateAds : ViewEffect()
...
}
Run Code Online (Sandbox Code Playgroud)
片段.kt
private val viewEvent: LiveData<Event<ViewEvent>> get() = _viewEvent
private val _viewEvent = MutableLiveData<Event<ViewEvent>>()
override fun onCreate(savedInstanceState: Bundle?) {
...
if (savedInstanceState == null)
_viewEvent.value = Event(ScreenLoad(...))
}
override fun onResume() {
super.onResume()
viewEvent.observe(viewLifecycleOwner, EventObserver { event ->
contentViewModel.processEvent(event)
})
}
Run Code Online (Sandbox Code Playgroud)
视图模型.kt
val viewState: LiveData<ViewState> get() = _viewState
val viewEffect: LiveData<Event<ViewEffect>> get() = _viewEffect
private val _viewState = MutableLiveData<ViewState>()
private val _viewEffect = MutableLiveData<Event<ViewEffect>>()
fun processEvent(event: ViewEvent) {
when (event) {
is ViewEvent.ScreenLoad -> {
// Populate view state based on network request response.
_viewState.value = ContentViewState(getMainFeed(...),...)
_viewEffect.value = Event(UpdateAds())
}
...
}
Run Code Online (Sandbox Code Playgroud)
LCE.kt
sealed class Lce<T> {
class Loading<T> : Lce<T>()
data class Content<T>(val packet: T) : Lce<T>()
data class Error<T>(val packet: T) : Lce<T>()
}
Run Code Online (Sandbox Code Playgroud)
结果.kt
sealed class Result {
data class PagedListResult(
val pagedList: LiveData<PagedList<Content>>?,
val errorMessage: String): ContentResult()
...
}
Run Code Online (Sandbox Code Playgroud)
存储库.kt
fun getMainFeed(...)= MutableLiveData<Lce<Result.PagedListResult>>().also { lce ->
lce.value = Lce.Loading()
/* Firestore request here. */.addOnCompleteListener {
// Save data.
lce.value = Lce.Content(ContentResult.PagedListResult(...))
}.addOnFailureListener {
lce.value = Lce.Error(ContentResult.PagedListResult(...))
}
}
Run Code Online (Sandbox Code Playgroud)
视图模型.kt
private fun getMainFeed(...) = Transformations.switchMap(repository.getFeed(...)) {
lce -> when (lce) {
// SwitchMap must be observed for data to be emitted in ViewModel.
is Lce.Loading -> Transformations.switchMap(/*Get data from Room Db.*/) {
pagedList -> MutableLiveData<PagedList<Content>>().apply {
this.value = pagedList
}
}
is Lce.Content -> Transformations.switchMap(lce.packet.pagedList!!) {
pagedList -> MutableLiveData<PagedList<Content>>().apply {
this.value = pagedList
}
}
is Lce.Error -> {
_viewEffect.value = Event(SnackBar(...))
Transformations.switchMap(/*Get data from Room Db.*/) {
pagedList -> MutableLiveData<PagedList<Content>>().apply {
this.value = pagedList
}
}
}
Run Code Online (Sandbox Code Playgroud)
片段.kt
contentViewModel.viewState.observe(viewLifecycleOwner, Observer { viewState ->
viewState.contentList.observe(viewLifecycleOwner, Observer { contentList ->
adapter.submitList(contentList)
})
...
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
7685 次 |
| 最近记录: |