如何将 StartActivityForResult 事件注册到 ViewModel?[MVVM]

4

我有一个在Java中使用MVVM实现Google登录的问题。

在这里,你会看到Google提供的一段普通的示例代码:

问题:

在你的Activity中:

@Override
public void onCreate(Bundle savedInstanceState) {
    /* Here is the Issue:
     * Google Object is defined in View - Activity
     * I would like to have Google Object defined in my ViewModel
     */
    GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN).requestEmail().build();
    mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
}

// when Google Button CLicked
@Override
public void onClick(View v) { signIn(); }

private void signIn() {
    /* Here is the Issue:
     * I have to get this process done in View Model
     * so view will not reference any Google Object
     */
    Intent signInIntent = mGoogleSignInClient.getSignInIntent();
    startActivityForResult(signInIntent, RC_SIGN_IN);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    // Below will be processed in ViewModel
    GoogleSignInClient.getSignInIntent(...);
    if (requestCode == RC_SIGN_IN) {
        Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
        handleSignInResult(task);
    }
}

问题:*见评论

所以我想到了以下的想法:

在Activity中:

// when Google Button CLicked
@Override
public void onClick(View v) { viewModel.loginGoogle(); }

private void subscribeUi() {
    // register startActivityForResult Event to ViewModel and set this activity as receiver...
    // viewModel.startActivityForResultEvent.setEventReceiver(this Activity)
    // How to do this?
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // send the result to View Model
    viewModel.onResultFromActivity(requestCode,resultCode,data);

    // escallate to super
    super.onActivityResult(requestCode, resultCode, data)
}

现在在ViewModel中:

public void viewModelOnCreate() {
    // This is what i want: Google object defined in View Model
    // but I dont know how to call startActivityForResult from here?
    GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN).requestEmail().build();
    mGoogleSignInClient = GoogleSignIn.getClient(getApplication(), gso);
}

// triggered when login button pressed
public void loginGoogle(){
    // send Trigger startActivityForResult(getGoogleSignInIntent(), GOOGLE_SIGN_IN) this event should be catch later in my Activity
    // How to do this?
    // maybe something like:
    // startActivityForResultEvent.sendEvent( ActivityNavigation.startActivityForResult startActivityForResult(getGoogleSignInIntent(), GOOGLE_SIGN_IN)
}

public void onResultFromActivity(int requestCode, int resultCode, Intent data){
    // do whatever needed here after received result from Google
    // for example:
    if (requestCode == RC_SIGN_IN) {
        Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
        handleSignInResult(task);
    }
}

有任何想法如何实现这个? 一直在努力想办法解决这个问题... 感谢并非常感激您的帮助 :)


loginGoogle()中不能将活动上下文作为参数传递吗? - Pavel Poley
可以,但是需要注意:ViewModel 绝不能引用 View、Lifecycle 或者任何可能持有 Activity 上下文引用的类。 - AnD
但是你可以将其封装在方法中,而不是将其存储为类变量。 - Pavel Poley
4个回答

0

你可以使用SingleLiveData打开新界面。参考: https://proandroiddev.com/livedata-with-single-events-2395dea972a8

  1. 您创建包含启动活动所需参数的类
  2. 在ViewModel中,您使用需要的参数来创建此类
  3. 您在ViewModel中创建单个LiveData字段,并从activity/fragment中观察它
  4. 您使用SingleLiveData发送此类

创建类:

public Enum Screen {
   LOGIN
}

在 ViewModel 中:

...

private SingleLiveData<Screen> onOpenScreen = new SingleLiveData<Screen>()

public SingleLiveData<Screen> observeScreenOpen() {
    return onOpenScreen
}

public void loginGoogle(){
    onOpenScreen.value = Screen.LOGIN
}

...

在Activity/Fragment中

viewModel.observeScreenOpen(this, new Observer<Screen> {screen->
    if(screen == Screen.LOGIN) {
        //start your activity here
    }
})

谢谢,但是你的链接是关于Kotlin的,我需要Java的。 另外,我在Java中有SingleLiveData,但我不确定如何将其实现到我的情况中 - 如果你有任何代码片段,我会非常感激 - 谢谢! - AnD
谢谢您的回复,有一件事,我不太确定ClassWithYourParams是什么?如何将其实现到我的情况中? - AnD
使用匿名类代替lambda表达式。启动Activity时请使用Activity/Fragment的引用。 - Peter Staranchuk
我的意思是你应该在你的Activity/Fragment中观察活动开始,从ViewModel发送此事件。不要在ViewModel中保留Android引用。 - Peter Staranchuk
谢谢你,Peter。这正是我在最初的问题中所询问的内容。我不确定如何从我的活动中观察(或注册)StartActivityForResult的结果。 - AnD
显示剩余4条评论

0

这似乎是由于替换了startActivityForResult的文档和函数所导致的沮丧:

注册一个请求以启动一个带有给定合同的结果活动。这将在与此调用方关联的注册表中创建一条记录,管理请求代码以及底层的Intent转换。这必须被无条件地调用,作为初始化路径的一部分,通常作为Activity或Fragment的字段初始化器。 如果此Fragment的主机是ActivityResultRegistryOwner,则将使用主机的ActivityResultRegistry。否则,这将使用Fragment的Activity的注册表。

请注意“这必须被无条件地调用,作为初始化路径的一部分”的注意事项。

还要注意这个IllegalStateException消息:

Fragment [this]尝试在创建后注册registerForActivityResult。片段必须在创建之前调用“registerForActivityResult()(即初始化,“onAttach()”或“onCreate()”)。

因此,我的建议是将合同和registerForActivityResult()放在您的Activity或Fragment的onCreate中,并将结果处理的任何操作放在视图模型/域类中的功能中,这基本上就是您已经在做的事情。


0

我认为你可以这样做,它不会持有context的全局引用,因此不会泄漏。

  public void loginGoogle(Context context){

         if(isSigningIn)
            return

         context.startActivityForResult(getGoogleSignInIntent(), GOOGLE_SIGN_IN)
         isSigningIn = true;
    }

如果视图模型包含这样的代码,那么这个方法(和逻辑)将无法通过单元测试进行测试,因为您无法模拟上下文。 - Peter Staranchuk
这不是为了此目的而设计的吗?请参考https://developer.android.com/reference/android/test/mock/MockContext。 - Allan Veloso

-1
我会在ViewModel中注册一个回调函数,以便Activity可以做出反应。然后,ViewModel可以拥有大部分业务逻辑,但不必引用Activity或Context,而Activity可以处理启动Intent的特定于Activity的内容。
例如:
回调接口:
interface OnSignInStartedListener {
    void onSignInStarted(GoogleSignInClient client);
}

视图模型:

public class ViewModel {
    private final OnSignInStartedListener mListener;

    public ViewModel(OnSignInStartedListener listener) {
        mListener = listener;
    }

    public void viewModelOnCreate() {
        // This is what i want: Google object defined in View Model
        // but I dont know how to call startActivityForResult from here?
        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN).requestEmail().build();
        mGoogleSignInClient = GoogleSignIn.getClient(getApplication(), gso);
    }

    public void loginGoogle() {
        // Invoke callback here to notify Activity
        mListener.onSignInStarted(mGoogleSignInClient);
    }
}

活动:

protected void onCreate(Bundle savedInstanceState) {
    ...
    mViewModel = new ViewModel(new OnSignInStartedListener() {
        @Override
        public void onSignInStarted(GoogleSignInClient client) {
            startActivityForResult(client.getSignInIntent(), RC_SIGN_IN);
        }
    });
    ...
}

@Override
public void onClick(View v) {
    // Invokes listener this activity created to start sign in flow
    viewModel.loginGoogle();
}

希望这能有所帮助!

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