Android MVVM startActivity 的最佳实践

Best practice for Android MVVM startActivity

我正在使用 MVVM 和 DataBinding 构建一个 Android 应用程序。我的 ViewModel 中有一个启动 Activity 的函数。 在 ViewModel 中调用 onClick 可以吗?

像这样。

public class MyViewModel {
    public void onClick(View view, long productId) {
        Context context = view.getContext();
        Intent intent = new Intent(context, ProductDetailActivity.class);
        intent.putExtra("productId", productId);
        context.startActivity(intent);
    }
}

在我的 XML 中:

...
android:onClick="@{(v) -> viewModel.onClick(v, viewModel.product.id)}">

或者将它移动到视图并从 EventBus 或 Rx 调用它并且在我的 ViewModel 中只有 POJO 是最佳实践吗?

将它放在 ViewModel 中绝对完美,但是您需要从 Activity/Fragment.

设置您的 ViewModel

您可以通过以下链接了解 MVVM 架构。

Approaching Android with MVVM
Android MVVM
https://github.com/ivacf/archi
People-MVVM
MVVM on Android: What You Need to Know

你的问题的答案是你的目标是什么?

如果您想使用 MVVM 来分离关注点,以便您可以对 Viewmodel 进行单元测试,那么您应该尝试保留所有需要 Context 与您的 Viewmodel 分开。 Viewmodel 包含您应用的核心业务逻辑,不应有任何外部依赖项。

不过我喜欢你要去的地方 :) 如果打开 Activity 的决定在于视图,那么很难为它编写 JUnit 测试。但是,您可以将一个对象传递给执行 startActivity() 调用的 Viewmodel。现在,在您的 单元测试 中,您可以简单地模拟此对象并验证是否打开了正确的 Activity

我的方法是,在您的 ViewModel 中:

val activityToStart = MutableLiveData<Pair<KClass<*>, Bundle?>>()

这允许您检查 Activity 的 class 是否已启动,以及 Bundle 中传递的数据。然后,在您的 Activity 中,您可以添加此代码:

viewModel.activityToStart.observe(this, Observer { value ->
    val intent = Intent(this, value.first.java)
    if(value.second != null)
        intent.putExtras(value.second)
    startActivity(intent)
})

正如 MVVM 的原则所指出的,只有 View (activity/fragment) 持有对 ViewModel 的引用,而 ViewModel 不应持有对任何 View 的引用。

在你的情况下,要开始 activity,我会这样做:

MyViewModel.class

public class MyViewModel {
public static final int START_SOME_ACTIVITY = 123;

 @Bindable
 private int messageId;

 public void onClick() {
  messageId = START_SOME_ACTIVITY;
  notifyPropertyChanged(BR.messageId); //BR class is automatically generated when you rebuild the project
 }

 public int getMessageId() {
        return messageId;
 }

 public void setMessageId(int message) {
        this.messageId = messageId;
 }

}

在你的MainActivity.class

@BindingAdapter({"showMessage"})
public static void runMe(View view, int messageId) {
    if (messageId == Consts.START_SOME_ACTIVITY) {      
        view.getContext().startActivity(new Intent(view.getContext(), SomeActivity.class));      
    }
}

@Override
protected void onPause() {
    super.onPause();
    finish(); //only call if you want to clear this activity after go to other activity
}

最后,在你的activity_main.xml

<Button    
  android:onClick="@{()-> myViewModel.onClick()}"    
  bind:showMessage="@{myViewModel.messageId}" />

根据数据绑定文档。 有两种方法可以做到这一点:

1- MethodReferences :您必须将视图作为参数传递给函数,否则会出现编译时错误。
如果您将使用这种方式,请在此处做一个单独的 class 作为示例来处理此类事件。

我的处理程序

public class MyHandler {
   public void onClick(View view, long productId) {
        Context context = view.getContext();
        Intent intent = new Intent(context, ProductDetailActivity.class);
        intent.putExtra("productId", productId);
        context.startActivity(intent);
    }
}

XML

<data>
        <variable
            name="viewModel"
            type="com.example.ViewModel"

        <variable
            name="myHandler"
            type="com.example.MyHandler" />

    </data>android:onClick="@{myHandler.onClick(viewModel.product.id)}">

2- Listener bindings :这里的例子不需要传递视图。
但是如果你想要 startActivity 让你的 viewModel 扩展 AndroidViewModel 并且你将使用应用程序对象。

视图模型

public class MyViewModel extends AndroidViewModel {
    public void onClick(long productId) {
        Intent intent = new Intent(getApplication(), ProductDetailActivity.class);
        intent.putExtra("productId", productId);
        context.startActivity(intent);
    }
}

XML

android:onClick="@{() -> viewModel.onClick(viewModel.product.id)}">

在 MVVM 中,我们可以为这个事件使用 LiveData。因为当 activity/Fragment 销毁时 ViewModel 还活着!所以最好的办法是 LiveData

1.Create Class 从 ViewModel:

调用 EventExtends
class Event : ViewModel() {

2.create 来自 LiveData 的字段:

private val _showSignIn = MutableLiveData<Boolean?>()

3.create 这个私有字段的方法:

val showSignIn: LiveData<Boolean?>
    get() = _showSignIn

4.create 您可以在 liveData 上设置值的方法:

 fun callSignIn() {
        _showSignIn.value = true
    }

最终事件Class:

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class Event : ViewModel() {

     private val _showSignIn = MutableLiveData<Boolean?>()

        val showSignIn: LiveData<Boolean?>
            get() = _showSignIn

        fun callSignIn() {
            _showSignIn.value = true
        }
  1. 调用您的 activity 或片段中的方法 :

来自 eventViewModel 的实例:

 private val eventViewModel = Event()

呼叫观察员:

 eventViewModel.showSignIn.observe(this, Observer {
            startActivity(Intent(this, MainActivity::class.java))
        })

如果您使用 data binding,您可以在 onClick XML 中调用 callSignIn() :

在变量标签中:

<variable
            name="eventViewModel"
            type=packageName.Event" />

 android:onClick="@{() -> eventViewModel.callSignIn()}" 

注意:不要忘记在 activity/fragment 中设置绑定:

  binding.eventViewModel = eventViewModel

我搜索最好的方法并找到它。我希望能帮助别人