Android ViewModel调用Activity方法

18

我在我的项目中使用了android AAC库和Android databinding库。我有AuthActivity和AuthViewModel两个类,它们都继承了android的ViewModel类。 在某些情况下,我需要请求Activity调用ViewModel的某些方法。例如,当用户单击Google认证或Facebook认证按钮时,这些按钮是在Activity类中初始化的(因为要初始化GoogleApiClient,我需要Activity上下文,而我无法将其传递给ViewModel,ViewModel也不能存储Activity字段)。所有与Google Api和Facebook API相关的逻辑都在Activity类中实现:

//google api initialization
googleApiClient = new GoogleApiClient.Builder(this)
                .enableAutoManage(this, this)
                .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
                .build();

//facebook login button
loginButton.setReadPermissions(Arrays.asList("email", "public_profile"));
loginButton.registerCallback(callbackManager,

我还需要调用登录意图,这也需要 Activity 上下文:

Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
startActivityForResult(signInIntent, GOOGLE_AUTH);

我无法在ViewModel类中请求Facebook登录和Google登录,也无法使用startActivity intent。因此,我创建了一个名为AuthActivityListener的接口类:

public interface AuthActivityListener {
    void requestSignedIn();

    void requestGoogleAuth();

    void requestFacebookAuth();

    void requestShowDialogFragment(int type);
}

在 Activity 类中实现监听器:

AuthActivityRequester authRequestListener = new AuthActivityRequester() {
        @Override
        public void requestSignedIn() {
            Intent intent = new Intent(AuthActivity.this, ScanActivity.class);
            startActivity(intent);
            AuthActivity.this.finish();
        }

        @Override
        public void requestGoogleAuth() {
            Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
            startActivityForResult(signInIntent, GOOGLE_AUTH);
        }
        ...

并将此监听器分配给视图模型类,以调用活动方法:

// in constructor
this.authRequester = listener;

// call activity method
public void onClickedAuthGoogle() {
        authRequester.requestGoogleAuth();
}

在 Google 或 Facebook 认证通过后,我从活动中调用视图模型方法:
@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        callbackManager.onActivityResult(requestCode, resultCode, data);
        if (requestCode == GOOGLE_AUTH) {
            GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
            if (result.isSuccess()) {
                GoogleSignInAccount acct = result.getSignInAccount();
                if (acct != null) {
                    viewModel.onGoogleUserLoaded(acct.getEmail(), acct.getId());
                } else {
                    viewModel.onGoogleUserLoaded("", "");
                }
            }
        }
    }

请问这种在视图模型和活动之间进行通信的方法正确吗?还是我需要找到另一种调用视图模型中活动方法的方式?


嘿,你找到了适合Google和Facebook登录的正确MVVM结构吗?我也在寻找同样的东西。 - iMDroid
遇到了同样的问题..想学习一下你是如何解决它的.. - Ehtesham Hasan
1
有没有人找到另一种解决方案,不需要从View传递引用到ViewModel? - Saini Arun
如果有人遇到这个问题,请查看这里 - Suyash Chavan
我认为如果外部API是这样设计的,让活动处理流程是可以接受的。目标应该是实现一个解决方案,遵循框架组件生命周期并以优雅的方式初始化/终止对象和服务,而不会泄漏资源。MVVM是一种方法,但你自己的解决方案也可能同样有效。 - Ankit
4个回答

8
有几种不同的方法可以实现这个。 在这里,我想与您分享我的方法。在我看来,这是最适合MVVM模式思想的方法。
正如所提到的:“视图模型不应该知道视图和引用它”。这样一来,视图模型调用活动方法的选项就很少了。首先,想到的是使用 监听器方法。但是,在我看来,这种方法有几个缺点:
  • View应该负责订阅/取消订阅ViewModel,因为它的生命周期最可能比ViewModel短。
  • 第一个缺点还导致一种情况:发生了某些事情,ViewModel应该调用View的方法,但View正在进行订阅/取消订阅;ViewModel也应该注意到空监听器的情况,因为它可能是null
  • 当增加新的ViewModel-Activity通信方法时,您将需要在ViewModelActivityListener接口中进行更改。
因此,监听器方法并不太适合。它看起来更像是MVP方法。为了消除上述缺点(或至少其中的一些),我创建了所谓的ViewModel事件方法。在这种方法中,ViewModel "发出"(或生成)它的事件,并让View观察它们。让我来展示一下我在说什么。
首先,我们需要一些 ViewModel 事件的表示方式。
abstract class ViewModelEvent {
    var handled: Boolean = false
        private set

    open fun handle(activity: BaseActivity) {
        handled = true
    }
}

正如您已经看到的那样,`handle()` 方法将发挥其魔力。当 `Activity` 处理接收到的事件时,它将把自己的实例作为参数传递给 `handle()` 方法。在这个方法中,我们可以调用任何 `Activity` 方法(或安全地将其转换为某个特定的 `Activity`)。`handled` 属性旨在防止 `Activity` 处理此 `ViewModelEvent` 两次。
此外,我们需要创建一些机制让 `ViewModel` 发出其事件。`LiveData` 最适合这些需求。它将在生命周期事件上取消观察者订阅,并存储最后发出的事件(这就是为什么 `ViewModelEvent` 应该具有上述 `handled` 属性的原因)。
abstract class BaseViewModel: ViewModel() {
    private val observableEvents = MutableLiveData<ViewModelEvent>()

    fun observeViewModelEvents(): LiveData<ViewModelEvent> = observableEvents

    protected fun postViewModelEvent(event: ViewModelEvent) {
        observableEvents.postValue(event)
    }
}

这里没有复杂的东西。只有一个MutableLiveData(作为LiveData公开),以及发出事件的方法。顺便说一下,在postViewModelEvent内部,我们可以检查调用该方法的线程,并使用MutableLiveData.postValueMutableLiveData.setValue

最后,就是Activity本身。

abstract class BaseActivity: Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        viewModel.observeViewModelEvents().observe(this, Observer {
            val event = it.takeUnless { it == null || it.handled } ?: return@Observer
            handleViewModelAction(event)
        })
    }

    protected open fun handleViewModelAction(event: ViewModelEvent) {
        event.handle(this)
    }
}

如您所见,一般事件可以在BaseActivity中处理,而一些特定的事件可以通过覆盖handleViewModelAction方法来处理。
这种方法可以根据具体需要进行更改。例如,ViewModelEvent不必与Activity实例一起使用,它可以用作“标记”事件,也可以传递某些特定参数以执行所需的操作等。 ViewModel事件方法使得ViewModel-Activity之间的通信更加稳健无缝。 Activity只需要订阅一次,就不会错过最新的ViewModel事件。

5

MVVM中最困难的部分是View Model不能知道视图并引用它们

这是一个非常严格的限制。

你有一些关于此的选项:

1. View Model方法接收上下文参数

您可以使方法从视图接收上下文(该方法从视图调用)。

之后,您可以实例化与上下文相关的变量。

如果您担心内存泄漏,请使用生命周期感知的AAC在视图暂停或停止时将其销毁,并在Activity或Fragment恢复或启动时重新实例化。

关于onActivityResult,我认为您的解决方案不错,因为API支持就是这样。

2. 使用数据绑定从视图获取上下文

在layout xml中,您可以通过事件监听器向视图本身发送视图。

<Button
    ....
    android:onClick=“@{(view) -> vm.onClickFacebookLogin(view)}”

然后您可以在ViewModel中接收视图并检索上下文

3. 使用AndroidViewModel

AndroidViewModel类与ViewModel类相同,只是没有应用程序上下文。

您可以使用Application上下文

gerApplication()

谢谢


0

从viewModel调用Activity的公共方法有两种方式。

  1. 通过使用上下文强制转换activity,例如

    MonthlyAttendance activity = (MonthlyAttendance) context;

    activity.getAttendance();

在ViewModel中需要Activity上下文。您可以通过ViewModelProvider.Factory将上下文传递给ViewModel。

  1. 通过MutableLiveData类和观察者的帮助来实现,例如

    MutableLiveData callMethod = new MutableLiveData<>();

    callMethod.setValue(true)

    viewModel.callMethod.observe(this, this::getAttendance);


0

你的方法很不错。但是你的接口有些依赖于活动,这意味着如果你正在重用视图,这些接口就没有用处了,或者可能需要创建新的接口来解决这个问题。

但是,如果你创建一个 Activity 的实例,那么你就可以控制它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接