【问题标题】:Android ViewModel call Activity methodsAndroid ViewModel 调用 Activity 方法
【发布时间】:2017-09-26 14:42:07
【问题描述】:

我在我的项目中使用了 android AAC 库和 Android 数据绑定库。我有 AuthActivity 和 AuthViewModel 扩展了 android 的 ViewModel 类。在某些情况下,我需要让 Activity 为 ViewModel 调用一些方法。 例如,当用户单击在 Activity 类中初始化的 Google Auth 或 Facebook Auth 按钮时(因为要初始化 GoogleApiClient,我需要无法传递给 ViewModel 的 Activity 上下文,视图模型无法存储 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);

我无法从视图模型类请求 facebook 登录和 google 登录,或 startActivity 意图,所以我创建了类接口 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("", "");
                }
            }
        }
    }

谁能解释一下视图模型和活动之间的这种通信方法是否正确,或者我需要找到另一种方式从视图模型中调用活动方法?

【问题讨论】:

  • 嘿,您找到适合 Google 和 Facebook 登录的 MVVM 结构了吗?我正在寻找相同的结构。
  • 面临同样的问题..想了解您是如何解决的..
  • 有没有人为此找到另一个解决方案,我们不必将引用从 View 传递给 ViewModel?
  • 如果有人遇到,请参阅this
  • 我认为如果外部 API 以这种方式设计,则可以进行异常处理并让活动处理流程。目标应该是实现一个解决方案,该解决方案遵循框架组件的生命周期,并以优雅的方式初始化/终止对象和服务,而不会泄漏资源。 MVVM 是一种方法,但您自己的解决方案也可以正常工作。

标签: android android-activity mvvm listener viewmodel


【解决方案1】:

有几种不同的方法可以做到这一点。 在这里,我想与您分享我的方法。在我看来,哪个最适合 MVVM 模式的思想。

如前所述 - “视图模型必须对视图一无所知并引用它”。这使得 View Model 如何调用 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

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

【讨论】:

    【解决方案2】:

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

    这是相当严格的限制。

    您对此有一些选择

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

    你可以让方法从视图中接收上下文(这个方法是从视图中调用的)。

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

    如果您知道内存泄漏,只需在视图暂停或停止使用 Lifecycle 感知 AAC 时将其销毁,并在恢复或启动 Activity 或 Fragment 时恢复。

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

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

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

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

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

    3.使用 AndroidViewModel

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

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

    一起使用
    gerApplication()
    

    谢谢

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-11-07
        • 1970-01-01
        • 2015-02-27
        • 1970-01-01
        • 2016-08-29
        • 2014-04-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多