我是MVP爱好者,但同时我胸襟开阔,我正努力提高对MVVM和数据绑定的了解:
我在这里分叉https://github.com/jpgpuyo/MVPvsMVVM
原始回购https://github.com/florina-muntenescu/MVPvsMVVM来自@FMuntenescu
我创建了几个分支。在其中之一中,我想显示2个不同的警报对话框,它们的样式不同,具体取决于对按钮执行的单击次数:
您可以在这里找到分支:https://github.com/jpgpuyo/MVPvsMVVM/tree/multiple_dialogs_databinding_different_style
我在视图模型中创建了2个可观察的字段,并添加了一个绑定适配器。
活动:
private void setupViews() {
buttonGreeting = findViewById(R.id.buttonGreeting);
buttonGreeting.setOnClickListener(v -> mViewModel.onGreetingClicked());
}
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
app:greetingType="@{viewModel.greetingType}"
app:greetingMessage="@{viewModel.greetingMessage}">
ViewModel:
public ObservableField<String> greetingMessage = new ObservableField<>();
public ObservableField<GreetingType> greetingType = new ObservableField<>();
public void onGreetingClicked() {
numberOfClicks++;
if (numberOfClicks % 2 == 0) {
mSubscription.add(mDataModel.getStandardGreeting()
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(greeting -> {
greetingMessage.set(greeting);
greetingType.set(GreetingType.STANDARD);
}));
} else {
mSubscription.add(mDataModel.getDroidconGreeting()
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(greeting -> {
greetingMessage.set(greeting);
greetingType.set(GreetingType.DROIDCON);
}));
}
}
MVVMBindingAdapter:
@BindingAdapter({"greetingType", "greetingMessage"})
public static void showAlertDialog(View view, GreetingType greetingType,
String greetingMessage) {
if (GreetingType.STANDARD.equals(greetingType)){
new DialogHelper().showStandardGreetingDialog(view.getContext(),
greetingMessage, greetingMessage);
} else if(GreetingType.DROIDCON.equals(greetingType)) {
new DialogHelper().showDroidconGreetingDialog(view.getContext(),
greetingMessage, greetingMessage);
}
}
对于MVVM,不确定如何将其实现为可通过Java单元测试进行全面测试的方法。我已经创建了绑定适配器,但是然后:
我需要绑定适配器中的if / else来显示一个或另一个对话框。
我不知道如何将对话框帮助器注入到绑定适配器中,所以除了使用powermock之外,我无法通过单元测试进行验证。
我为每个对话框添加了不同的样式,因为如果我不放置样式,我们可以认为对话框的标题和消息是从数据层检索的,但是考虑到Android样式是从数据层检索的,这很奇怪。
是否可以将对话框助手插入MVVM以解决此问题并使代码可测试?
哪种方法是使用MVVM管理警报对话框的最佳方法?
我用于MVVM的解决方案如下所示。
[从LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)的SO答案中提到的Show Dialog from ViewModel in Android MVVM Architecture中篇文章中提到的JoseAlcérreca的文章中,我选择了第四个选项“推荐:使用事件包装器”。原因是我可以根据需要查看消息。另外,我从observeEvent()
添加了this comment in Jose's Gist扩展方法。
我的最终代码是:
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) })
}
用法就是这样(我的示例在数据同步完成时触发事件):
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)
}
}
并且通过使用observeEvent()
来使用它来获得简洁的代码:
exampleViewModel.synchronizationResult.observeEvent(this) { result ->
// We will be delivered "result" only once per change
}