Android ViewModel调用Activity方法

Vla*_*nov 11 android listener mvvm viewmodel android-activity

我正在我的项目中使用android AAC库和Android数据绑定库.我有AuthActivity和AuthViewModel扩展了android的ViewModel类.在某些情况下,我需要让Activity为ViewModel调用一些方法.例如,当用户点击Google Auth或Facebook Auth按钮时,它在Activity类中初始化(因为初始化GoogleApiClient我需要Activity上下文,我无法传递给ViewModel,视图模型无法存储Activity字段).在Activity类中实现了Google Api和Facebook API的所有逻辑:

//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,
Run Code Online (Sandbox Code Playgroud)

此外,我需要调用登录意图,这也需要活动上下文:

Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
startActivityForResult(signInIntent, GOOGLE_AUTH);
Run Code Online (Sandbox Code Playgroud)

我不能请求facebook登录和google登录,或者从视图模型类请求startActivity intent,所以我创建了类接口AuthActivityListener:

public interface AuthActivityListener {
    void requestSignedIn();

    void requestGoogleAuth();

    void requestFacebookAuth();

    void requestShowDialogFragment(int type);
}
Run Code Online (Sandbox Code Playgroud)

在活动类中实现监听器:

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);
        }
        ...
Run Code Online (Sandbox Code Playgroud)

并在视图模型类中分配此侦听器以调用活动方法:

// in constructor
this.authRequester = listener;

// call activity method
public void onClickedAuthGoogle() {
        authRequester.requestGoogleAuth();
}
Run Code Online (Sandbox Code Playgroud)

在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("", "");
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

任何人都可以解释一下,视图模型和活动之间的这种沟通方式是对的,还是我需要找到另一种从视图模型中调用活动方法的方法?

Dem*_*god 6

关于如何做到这一点,有几种不同的方法。在这里,我想与您分享我的方法。在我看来,这最适合 MVVM 模式意识形态。

如前所述 - “视图模型必须对视图一无所知并引用它”。这对于 View Model 如何调用 Activity 方法的选择并不多。首先,想到的是侦听器方法。但我认为这种方法有几个缺点:

  • View应照顾订阅/退订/从ViewModel,因为它的寿命很可能比短ViewModel
  • 第一个缺点还会导致发生某些事情并且ViewModel应该调用View的方法但View介于订阅/取消订阅之间的情况;ViewModel也应该意识到空听众的情况,因为它可以null
  • 添加ViewModel-Activity通信的新方法时,您必须在ViewModelActivityListener界面中进行更改。

所以Listener方法不太适合。它看起来更像是一种 MVP 方法。为了消除上述缺点(或至少其中一些),我创建了我称之为ViewModel Events 的方法。在这种方法中,ViewModel“发出”(或生成)它的事件并让View观察它们。让我展示一下我在说什么。

首先,我们需要对ViewModel事件进行一些表示。

abstract class ViewModelEvent {
    var handled: Boolean = false
        private set

    open fun handle(activity: BaseActivity) {
        handled = true
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您已经看到的,该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)
    }
}
Run Code Online (Sandbox Code Playgroud)

这里没有什么复杂的。只是一个MutableLiveData(暴露为LiveData)和一个发出事件的方法。顺便说一下,在里面postViewModelEvent我们可以检查调用这个方法的线程并使用MutableLiveData.postValueor MutableLiveData.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)
    }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,可以在 中处理一般事件BaseActivity,而可以通过覆盖该handleViewModelAction方法来处理某些特定事件。

可以根据特定需求更改此方法。例如,ViewModelEvent不必使用Activity实例,可以用作“标记”事件,或者它可以为所需的操作传递一些特定参数等。

视图模型活动的方法使视图模型,活动通信稳健和无缝。Activity将不得不订阅一次,它不会错过最新ViewModel的事件。


MJ *_*dio 5

MVVM 最困难的部分是视图模型必须不了解视图并引用它们

\n\n

这是相当强的限制。

\n\n

你对此有一些选择

\n\n

1. 查看接收上下文参数的模型方法

\n\n

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

\n\n

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

\n\n

如果您意识到内存泄漏,只需在视图暂停或停止时使用生命周期感知 AAC 销毁它,并在恢复或启动 Activity 或 Fragment 时恢复。

\n\n

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

\n\n

2. 通过数据绑定从视图中获取上下文

\n\n

在布局 xml 中,您可以使用事件侦听器发送视图本身。

\n\n
<Button\n    ....\n    android:onClick=\xe2\x80\x9c@{(view) -> vm.onClickFacebookLogin(view)}\xe2\x80\x9d\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后您可以接收视图并从 Viewmodel 中的视图检索上下文

\n\n

3.使用AndroidViewModel

\n\n

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

\n\n

您可以使用应用程序上下文

\n\n
gerApplication()\n
Run Code Online (Sandbox Code Playgroud)\n\n

谢谢

\n


小智 0

嗯,你的方法很好。但不知何故,你的界面取决于活动,这意味着如果你重用你的视图,这些界面没有用处,或者可能对于这种情况,你必须创建新的界面来解决你的问题。

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