Android ViewModel调用Activity方法

时间:2017-09-26 14:42:08

标签: android android-activity mvvm listener viewmodel

我在我的项目中使用了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,

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

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

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

public interface AuthActivityListener {
    void requestSignedIn();

    void requestGoogleAuth();

    void requestFacebookAuth();

    void requestShowDialogFragment(int type);
}

在活动类中实现侦听器:

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("", "");
                }
            }
        }
    }

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

3 个答案:

答案 0 :(得分:0)

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

这是一个很强的限制。

您对此有一些选择

1。查看包含上下文参数的模型方法

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

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

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

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

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

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

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

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

3。使用AndroidViewModel

AndroidViewModel类与ViewModel类相同,但没有Application上下文。

您可以将应用程序上下文与

一起使用
gerApplication()

谢谢

答案 1 :(得分:0)

关于如何执行此操作,有几种不同的方法。 在这里,我想与您分享我的方法。我认为哪种方法最适合MVVM模式思想。

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

  • 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

    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的活动。

答案 2 :(得分:-1)

你的方法很好。但不知何故,你的界面取决于活动意味着如果你重复使用你的视图这些界面没有用,或者可能是那个场景,你必须创建新的界面来解决你的问题。

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