Android MVP vs MVVM:如何管理MVVM中的警报对话框并提高可测试性

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

我是MVP爱好者,但同时我思想开放,我正在努力提高我对MVVM和数据绑定的知识:

我在这里分叉了

原始回购协议 来自@FMuntenescu

我创建了几个分支。在其中一个对话框中,我想显示两个不同的警报对话框,根据按钮上执行的单击次数,它们具有不同的样式:

  • 偶数次单击->显示标准对话框
  • 奇数次单击->显示droidcon对话框
您可以在这里找到分支:

我在视图模型中创建了2个可观察字段,并添加了一个绑定适配器

活动:

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
}