Android MVVM startActivity 的最佳实践
Posted
技术标签:
【中文标题】Android MVVM startActivity 的最佳实践【英文标题】:Best practice for Android MVVM startActivity 【发布时间】:2017-03-21 09:30:11 【问题描述】:我正在使用 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)">
或者将它移动到 View 并从 EventBus 或 Rx 调用它并且我的 ViewModel 中只有 POJO 是否是最佳实践?
【问题讨论】:
【参考方案1】:您的问题的答案是您的目标是什么?
如果您想使用 MVVM 来分离关注点,以便您可以对 Viewmodel
进行单元测试,那么您应该尝试将需要 Context
的所有内容与您的 Viewmodel
分开。 Viewmodel
包含您应用的核心业务逻辑,应该没有外部依赖项。
但是我喜欢你要去哪里:) 如果决定打开哪个 Activity 在于视图,那么为它编写一个 JUnit 测试是非常非常困难的。但是,您可以将对象传递给执行startActivity()
调用的Viewmodel
。现在在您的单元测试中,您可以简单地模拟这个对象并验证是否打开了正确的Activity
【讨论】:
"Viewmodel 包含你应用的核心业务逻辑,应该没有外部依赖。" - 我认为它总是会有外部依赖。我看不出在没有数据绑定的情况下使用 ViewModel 有什么意义,它是特定于 android 的,只需检查 example import: "import android.databinding.ObservableBoolean;" @LLL,你在某种程度上是对的。问题是想要达到什么目标。对我来说,最重要的是可测试性。如果我依赖于View
或Context
,我只能通过Robolectric 进行测试。这些测试很慢并且需要大量开销。所以我正在寻找的是简单的单元测试,它可以在很短的时间内运行并且易于实现。对数据绑定库的依赖并没有减少这一点。
@Kaskasi,所以有任何方法可以实现这样的功能。我不想将 Context 保留在我的 ViewModel 类中,因为它会造成内存泄漏。我也不想通过任何参考。因此,如果我想将一些字符串资源从视图传递给 viewModel,或者我想在视图中调用一个方法来启动一些活动,那么我该如何实现它。
@SainiArun 你可以做类似的事情:***.com/questions/31883415/… 所以你要做的就是将一个抽象传递给你的 ViewModel 或 Presenter。该抽象(接口)可以有一个方法String getString(@StringRes int id)
。在该接口的具体实现中,您将实现getString
并可以将其委托给上下文。获取字符串()。这样,ViewModel 只能看到一个抽象,您可以模拟它以进行测试
@Kaskasi,感谢您的回复。实际上我已经知道这种方法并且我也在使用它。我并不真正关心测试。我唯一担心的是不要使用任何对视图的引用,因为这会导致内存泄漏,我不希望这样。那么告诉我,如果我们使用视图中实现并被 ViewModel 引用的接口来获取或设置视图中的数据,会不会导致内存泄漏?【参考方案2】:
我的做法是,在您的 ViewModel 中:
val activityToStart = MutableLiveData<Pair<KClass<*>, Bundle?>>()
这可以让你检查Activity启动的类,以及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)
)
【讨论】:
谢谢,但对我不起作用。你能解释一下吗? XML中view标签的onClick方法如何使用?【参考方案3】:将它放在ViewModel
中绝对完美,但是您需要将ViewModel
设置为Activity
/Fragment
。
您可以通过以下链接了解 MVVM 架构。
Approaching Android with MVVMAndroid MVVMhttps://github.com/ivacf/archiPeople-MVVMMVVM on Android: What You Need to Know
【讨论】:
这些示例中的大多数在 ViewModel 中都有上下文引用。对我来说,这不是真正的 MVVM。 @MwBakker 我认为 MVVM 最重要的是层与层之间的逻辑分离,因此,视图应该负责处理 UI,仅此而已!因此,在这种情况下,我相信 View Model 引用应用程序上下文比强制视图不执行任何 UI 任务的危害要小。顺便说一句,在许多其他情况下,我们可能需要 ViewModel 中的上下文,这就是为什么 Android 有一个具有上下文的 ViewModel 版本! 认为您应该在 viewmodel 中没有 android 包?【参考方案4】:正如 MVVM 的原理所指出的,只有 View(活动/片段)持有对 ViewModel 的引用,而 ViewModel 不应该持有对任何 View 的引用。
在你的情况下,要开始一项活动,我会这样做:
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" />
【讨论】:
在视图模型中值保持不变,因此它可能会一次又一次地开始活动【参考方案5】:根据数据绑定文档。 有两种方法可以做到这一点:
1- MethodReferences :您必须将视图作为参数传递给函数,否则会出现编译时错误。 如果您将使用这种方式,请在此处创建一个单独的类作为示例来处理此类事件。
我的处理程序
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 并且你将使用 applicaion 对象。
视图模型
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)">
【讨论】:
在第 2 部分中,context
并未实际定义。假设你想使用getApplication()
作为上下文,不幸的是你必须设置FLAG_INTENT_NEW_TASK,否则你会崩溃。 (有一些最新版本的 Android 没有崩溃,但这被认为是一个错误)。见***.com/q/3918517/3175580
您可以通过 xml 轻松传递它。 android:onClick="@(v) -> viewModel.onClick(v,viewModel.product.id)"> .. 代码:public void onClick(View v,long productId) ... v.getContext()
【参考方案6】:
在 MVVM 中,我们可以将 LiveData
用于此 Event 。因为当activity/Fragment
被销毁时,ViewModel 是活着的!所以最好的方法是LiveData
1.创建类调用Event
和Extends
它来自ViewModel
:
class Event : ViewModel()
2.从 LiveData 创建字段:
private val _showSignIn = MutableLiveData<Boolean?>()
3.为这个私有字段创建方法:
val showSignIn: LiveData<Boolean?>
get() = _showSignIn
4.create 方法,您可以在 liveData 上设置值:
fun callSignIn()
_showSignIn.value = true
最终事件类:
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
-
在您的活动或片段中调用方法:
来自 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
我寻找最好的方法并找到它。我希望能帮助别人
【讨论】:
小心这种方法。如果你按下按钮,Activity 会启动,然后你返回旋转屏幕,LiveData 值会再次发送,所以 Activity 会意外地再次启动。因此,对于这种事件,您应该使用其他方法,搜索 SingleLiveEvent 或 EventWrapper。以上是关于Android MVVM startActivity 的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章