Android MVP vs MVVM:如何管理MVVM中的警报对话框并提高可测试性
我是MVP爱好者,但同时我思想开放,我正在努力提高我对MVVM和数据绑定的知识: 我在这里分叉了 原始回购协议 来自@FMuntenescu 我创建了几个分支。在其中一个对话框中,我想显示两个不同的警报对话框,根据按钮上执行的单击次数,它们具有不同的样式:Android MVP vs MVVM:如何管理MVVM中的警报对话框并提高可测试性,android,mvvm,android-databinding,android-mvp,Android,Mvvm,Android Databinding,Android Mvp,我是MVP爱好者,但同时我思想开放,我正在努力提高我对MVVM和数据绑定的知识: 我在这里分叉了 原始回购协议 来自@FMuntenescu 我创建了几个分支。在其中一个对话框中,我想显示两个不同的警报对话框,根据按钮上执行的单击次数,它们具有不同的样式: 偶数次单击->显示标准对话框 奇数次单击->显示droidcon对话框 您可以在这里找到分支: 我在视图模型中创建了2个可观察字段,并添加了一个绑定适配器 活动: private void setupViews(){ buttonGre
- 偶数次单击->显示标准对话框
- 奇数次单击->显示droidcon对话框
private void setupViews(){
buttonGreeting=findViewById(R.id.buttonGreeting);
buttonGreeting.setOnClickListener(v->mViewModel.onGreetingClicked());
}
视图模型:
public observefield greetingMessage=new observefield();
公共ObservableField greetingType=新ObservableField();
public void onGreetingClicked(){
点击次数++;
如果(点击次数%2==0){
添加(mDataModel.getStandardGreeting()
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.订阅(问候语->{
greetingMessage.set(问候语);
greetingType.set(greetingType.STANDARD);
}));
}否则{
添加(mDataModel.getDroidconGreeting())
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.订阅(问候语->{
greetingMessage.set(问候语);
greetingType.set(greetingType.DROIDCON);
}));
}
}
MVVMBindingAdapter:
@BindingAdapter({“greetingType”、“greetingMessage”})
公共静态void showAlertDialog(视图、问候类型、问候类型、,
字符串问候信息){
if(GreetingType.STANDARD.equals(GreetingType)){
新建DialogHelper().showStandardGreetingDialog(view.getContext(),
问候语,问候语);
}else if(GreetingType.DROIDCON.equals(GreetingType)){
新建DialogHelper().showDroidconGreetingDialog(view.getContext(),
问候语,问候语);
}
}
对于MVVM,不确定如何实现它,使其完全可通过java单元测试进行测试。我已经创建了绑定适配器,但是:
- 我需要一个if/else-in绑定适配器来显示一个或另一个对话框
- 我不知道如何将对话助手注入绑定适配器,所以除了powermock之外,我无法使用单元测试进行验证
我为每一个对话框添加了不同的样式,因为如果我不设置样式,我们可以认为对话框中的标题和消息是从数据层中检索出来的,但是从数据层考虑Android风格是很奇怪的。 在MVVM中插入一个对话助手来解决这个问题并使代码可测试,可以吗
使用MVVM管理警报对话框的最佳方法是什么?我用于MVVM的解决方案是混合的,如下所示 从Jose Alcérreca的文章中,我选择了第四个选项“推荐:使用事件包装器”。原因是,如果需要,我可以偷看信息。此外,我还从中添加了
observeEvent()
扩展方法
我的最终代码是:
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
* See:
* https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150
* https://gist.github.com/JoseAlcerreca/e0bba240d9b3cffa258777f12e5c0ae9
*/
open class LiveDataEvent<out T>(private val content: T) {
@Suppress("MemberVisibilityCanBePrivate")
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
inline fun <T> LiveData<LiveDataEvent<T>>.observeEvent(owner: LifecycleOwner, crossinline onEventUnhandledContent: (T) -> Unit) {
observe(owner, Observer { it?.getContentIfNotHandled()?.let(onEventUnhandledContent) })
}
经过一番研究,我找到了方法。我还发现了Hannes Dorfmann提出模型视图意图方法的地方。使用这种方法,我可以创建两种不同的状态:每个对话框一个。无论如何,这将是另一种方法,在我看来,MVP仍然比MVVM或MVI提供更多的可测试性,因为演示者知道这个观点。
class ExampleViewModel() : ViewModel() {
private val _synchronizationResult = MutableLiveData<LiveDataEvent<SyncUseCase.Result>>()
val synchronizationResult: LiveData<LiveDataEvent<SyncUseCase.Result>> = _synchronizationResult
fun synchronize() {
// do stuff...
// ... when done we get "result"
_synchronizationResult.value = LiveDataEvent(result)
}
}
exampleViewModel.synchronizationResult.observeEvent(this) { result ->
// We will be delivered "result" only once per change
}