'How to register StartActivityForResult event to ViewModel? [MVVM]
I have a problem implementing Google signin with MVVM in Java. here, in a normal way you will see this sample code from Google:
PROBLEM:
in your 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);
}
}
QUESTIONS: *see comment
so I came out with Idea Below:
in 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)
}
now in 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);
}
}
any Idea how to get this achieved? been scratching my head to get this done... thanks and Appreciate the help :) }
Solution 1:[1]
I think you can do the following, it is not hold global reference to context so it will not leak
public void loginGoogle(Context context){
if(isSigningIn)
return
context.startActivityForResult(getGoogleSignInIntent(), GOOGLE_SIGN_IN)
isSigningIn = true;
}
Solution 2:[2]
You can use SingleLiveData to open new screen. See: https://proandroiddev.com/livedata-with-single-events-2395dea972a8
- You create class with all necessary parameters to start activity
- In ViewModel you create this class with parameters you need
- You create single live data field in your ViewModel and observe it from activity/fragment
- You send this class with SingleLiveData
create class:
public Enum Screen {
LOGIN
}
in ViewModel:
...
private SingleLiveData<Screen> onOpenScreen = new SingleLiveData<Screen>()
public SingleLiveData<Screen> observeScreenOpen() {
return onOpenScreen
}
public void loginGoogle(){
onOpenScreen.value = Screen.LOGIN
}
...
in activity/fragment
viewModel.observeScreenOpen(this, new Observer<Screen> {screen->
if(screen == Screen.LOGIN) {
//start your activity here
}
})
Solution 3:[3]
This seems to be disencourage by the documentation and function that replaced startActivityForResult:
Register a request to start an activity for result, designated by the given contract. This creates a record in the registry associated with this caller, managing request code, as well as conversions to/from Intent under the hood. This must be called unconditionally, as part of initialization path, typically as a field initializer of an Activity or Fragment. If the host of this fragment is an ActivityResultRegistryOwner the ActivityResultRegistry of the host will be used. Otherwise, this will use the registry of the Fragment's Activity.
Attention for the "This must be called unconditionally, as part of initialization path".
Also notice this IlliegalStateException message:
Fragment [this] is attempting to registerForActivityResult after being created. Fragments must call "registerForActivityResult() before they are created (i.e. initialization, "onAttach(), or onCreate()).
So my suggestion is to put the contract and registerForActivityResult() on your Activity or Fragment onCreate and whatever you will be doing with the result in a function in your view model / domain class, which is basically what you are already doing.
Solution 4:[4]
What I would do is register a callback in the ViewModel that is invoked that the Activity can react to. Then the ViewModel can own the bulk of the business logic but does not have to have a reference to the Activity or Context and the Activity can deal with the Activity-specific stuff of launching an Intent.
Example:
Callback Interface:
interface OnSignInStartedListener {
void onSignInStarted(GoogleSignInClient client);
}
ViewModel:
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);
}
}
Activity:
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();
}
Hope that helps!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Pavel Poley |
| Solution 2 | |
| Solution 3 | Allan Veloso |
| Solution 4 | dominicoder |
