Error in Fragment: "Already managing a GoogleApiClient with id 0"
JavaAndroidAndroid FragmentsJava Problem Overview
Everything works right the first time, if you launch a second time you see this error:
FATAL EXCEPTION: main
Process: ro.vrt.videoplayerstreaming, PID: 23662
java.lang.IllegalStateException: Already managing a GoogleApiClient with id 0
at com.google.android.gms.common.internal.zzx.zza(Unknown Source)
at com.google.android.gms.common.api.internal.zzw.zza(Unknown Source)
at com.google.android.gms.common.api.GoogleApiClient$Builder.zza(Unknown Source)
at com.google.android.gms.common.api.GoogleApiClient$Builder.zze(Unknown Source)
at com.google.android.gms.common.api.GoogleApiClient$Builder.build(Unknown Source)
at ro.vrt.videoplayerstreaming.Login.onCreateView(Login.java:75)
at android.support.v4.app.Fragment.performCreateView(Fragment.java:1974)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1067)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1252)
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:738)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1617)
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:517)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5849)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:763)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:653)
Here's my code:
public class Login extends Fragment implements
GoogleApiClient.OnConnectionFailedListener,
View.OnClickListener {
private static final String TAG = "SignInActivity";
private static final int RC_SIGN_IN = 9001;
private GoogleApiClient mGoogleApiClient;
private TextView mStatusTextView;
private ProgressDialog mProgressDialog;
private static String url;
private static View view;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (view != null) {
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null)
parent.removeView(view);
}
try {
view = inflater.inflate(R.layout.activity_login, container, false);
// Views
mStatusTextView = (TextView) view.findViewById(R.id.status);
// Button listeners
view.findViewById(R.id.sign_in_button).setOnClickListener(this);
view.findViewById(R.id.sign_out_button).setOnClickListener(this);
view.findViewById(R.id.disconnect_button).setOnClickListener(this);
// [START configure_signin]
// Configure sign-in to request the user's ID, email address, and basic
// profile. ID and basic profile are included in DEFAULT_SIGN_IN.
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.build();
// [END configure_signin]
// [START build_client]
// Build a GoogleApiClient with access to the Google Sign-In API and the
// options specified by gso.
mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
.enableAutoManage(getActivity()/* FragmentActivity */, this /* OnConnectionFailedListener */)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
// [END build_client]
// [START customize_button]
// Customize sign-in button. The sign-in button can be displayed in
// multiple sizes and color schemes. It can also be contextually
// rendered based on the requested scopes. For example. a red button may
// be displayed when Google+ scopes are requested, but a white button
// may be displayed when only basic profile is requested. Try adding the
// Scopes.PLUS_LOGIN scope to the GoogleSignInOptions to see the
// difference.
SignInButton signInButton = (SignInButton) view.findViewById(R.id.sign_in_button);
signInButton.setSize(SignInButton.SIZE_STANDARD);
signInButton.setScopes(gso.getScopeArray());
// [END customize_button]
} catch (InflateException e) {
/* map is already there, just return view as it is */
}
super.onCreate(savedInstanceState);
return view;
}
@Override
public void onStart() {
super.onStart();
OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
if (opr.isDone()) {
// If the user's cached credentials are valid, the OptionalPendingResult will be "done"
// and the GoogleSignInResult will be available instantly.
Log.d(TAG, "Got cached sign-in");
GoogleSignInResult result = opr.get();
handleSignInResult(result);
} else {
// If the user has not previously signed in on this device or the sign-in has expired,
// this asynchronous branch will attempt to sign in the user silently. Cross-device
// single sign-on will occur in this branch.
showProgressDialog();
opr.setResultCallback(new ResultCallback<GoogleSignInResult>() {
@Override
public void onResult(GoogleSignInResult googleSignInResult) {
//adaugat de mine sa porneacsa singur cererea de logare
signIn();
//fin
hideProgressDialog();
handleSignInResult(googleSignInResult);
}
});
}
}
// [START onActivityResult]
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...);
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
handleSignInResult(result);
}
}
// [END onActivityResult]
// [START handleSignInResult]
private void handleSignInResult(GoogleSignInResult result) {
Log.d(TAG, "handleSignInResult:" + result.isSuccess());
if (result.isSuccess()) {
// Signed in successfully, show authenticated UI.
GoogleSignInAccount acct = result.getSignInAccount();
mStatusTextView.setText(getString(R.string.signed_in_fmt, acct.getDisplayName() + " Your token " + acct.getId()));
url = "http://grupovrt.ddns.net:81/index.php?token="+acct.getId();
updateUI(true);
} else {
// Signed out, show unauthenticated UI.
updateUI(false);
}
}
// [END handleSignInResult]
// [START signIn]
private void signIn() {
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
startActivityForResult(signInIntent, RC_SIGN_IN);
}
// [END signIn]
// [START signOut]
private void signOut() {
Auth.GoogleSignInApi.signOut(mGoogleApiClient).setResultCallback(
new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
// [START_EXCLUDE]
updateUI(false);
// [END_EXCLUDE]
}
});
}
// [END signOut]
// [START revokeAccess]
private void revokeAccess() {
Auth.GoogleSignInApi.revokeAccess(mGoogleApiClient).setResultCallback(
new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
// [START_EXCLUDE]
updateUI(false);
// [END_EXCLUDE]
}
});
}
// [END revokeAccess]
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
// An unresolvable error has occurred and Google APIs (including Sign-In) will not
// be available.
Log.d(TAG, "onConnectionFailed:" + connectionResult);
}
private void showProgressDialog() {
if (mProgressDialog == null) {
mProgressDialog = new ProgressDialog(getActivity());
mProgressDialog.setMessage(getString(R.string.loading));
mProgressDialog.setIndeterminate(true);
}
mProgressDialog.show();
}
private void hideProgressDialog() {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.hide();
}
}
private void updateUI(boolean signedIn) {
if (signedIn) {
getView().findViewById(R.id.sign_in_button).setVisibility(View.GONE);
getView().findViewById(R.id.sign_out_and_disconnect).setVisibility(View.VISIBLE);
} else {
mStatusTextView.setText(R.string.signed_out);
getView().findViewById(R.id.sign_in_button).setVisibility(View.VISIBLE);
getView().findViewById(R.id.sign_out_and_disconnect).setVisibility(View.GONE);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.sign_in_button:
signIn();
break;
case R.id.sign_out_button:
signOut();
break;
case R.id.disconnect_button:
revokeAccess();
break;
}
}
}
As I understand it, the problem is in these lines:
mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
.enableAutoManage(getActivity()/* FragmentActivity */, this /* OnConnectionFailedListener */)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
I tried explicitly passing an id of 0
:
.enableAutoManage(getActivity() /* FragmentActivity */, 0, this /* OnConnectionFailedListener */)
but that still didn't work.
What am I missing?
Java Solutions
Solution 1 - Java
You should call stopAutoManage()
in the onPause()
method of your Fragment
:
@Override
public void onPause() {
super.onPause();
mGoogleClient.stopAutoManage(getActivity());
mGoogleClient.disconnect();
}
Solution 2 - Java
You should call stopAutoManage()
in the onPause()
method of your Fragment
like so:
@Override
public void onPause() {
super.onPause();
mGoogleApiClient.stopAutoManage(getActivity());
mGoogleApiClient.disconnect();
}
Solution 3 - Java
To avoid further issue
@Override
public void onStop() {
super.onStop();
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
mGoogleApiClient.stopAutoManage((Activity) context);
mGoogleApiClient.disconnect();
}
}
Solution 4 - Java
I faced a similar issue when I placed a login button in two different Fragment
s belonging to the same Activity
.
I solved this issue by assigning different ids to each automatically-managed GoogleApiClient
.
For example, in Fragment
1, while creating my GoogleApiClient
object I assigned 0 as the id:
mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
.enableAutoManage(getActivity(), 0, this /* OnConnectionFailedListener */)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
In Fragment
2, while creating my GoogleApiClient
object I assigned 1 as the id:
mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
.enableAutoManage(getActivity(), 1, this /* OnConnectionFailedListener */)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
Solution 5 - Java
The official doc for enableAutoManage says this:
> At any given time, only one auto-managed client is allowed per id. To reuse an id you must first call stopAutoManage(FragmentActivity) on the previous client.
Your code is using the version of enableAutoManage without a clientId parameter, so it's defaulting to 0. Below I explain why you will have multiple auto-managed clients for clientId 0, which is what the above documentation is warning against.
After your Login Fragment is attached to a FragmentActivity, it tells that activity to start managing a new instance of GoogleApiClient. But what if the FragmentActivity is already managing another instance of GoogleApiClient? That's when you get the error.
There are a few possible scenarios that can lead up to this multiple-GoogleApiClients-per-FragmentActivity situation.
- The FragmentActivity has another Fragment in addition to Login that creates a GoogleApiClient and also asks the FragmentActivity to manage it.
- The FragmentActivity itself creates a GoogleApiClient and starts managing it before Login Fragment is attached to the FragmentActivity.
- Maybe you add Login Fragment in a FragmentTransaction and you call addToBackStack. Then the user taps back, then later somehow the Login Fragment is re-attached. In this case, the important Login Activity method calls are onCreateView -> onDestroyView -> onCreateView as shown here:
This is problematic because the second call to Login.onCreateView tries to have FragmentActivity manage a second GoogleApiClient.
If I were you, I'd seriously consider creating the GoogleApiClient in the activity instead of in any fragments. Then you could either do the work that requires GoogleApiClient in the Activity, or continue to do it in Login Fragment after getting the GoogleApiClient from the Activity like so:
private GoogleApiClient googleApiClient;
@Override
void onAttach(Activity activity) {
super.onAttach(activity);
googleApiClient = activity.getGoogleApiClient();
}
@Override
void onDetach() {
super.onDetach();
googleApiClient = null;
}
Solution 6 - Java
I suggest that you initialize your mGoogleApiClient
in onCreate()
instead of in onCreateView()
.
As pointed out by @vlazzle, onCreateView()
can be called more than once during a single Activity
's lifespan.
Solution 7 - Java
You should call stopAutoManage()
in the onDestroy()
method of your fragment like so:
@Override
public void onDestroy() {
super.onDestroy();
mGoogleApiClient.stopAutoManage(getActivity());
mGoogleApiClient.disconnect();
}
Solution 8 - Java
This works for me to avoid crashing issues using kotlin
private lateinit var googleApiClient: GoogleApiClient
And then just verify if the variable is already initialized
if(!::googleApiClient.isInitialized) {
googleApiClient = GoogleApiClient.Builder(context)
.enableAutoManage(activity, this)
.addApi(Auth.GOOGLE_SIGN_IN_API, options)
.build()
}
Solution 9 - Java
The checked answer will not work if you use Login
fragment in an Activity multiple times, because adding the fragment sequentially in a short time will lead the same crash. Android sometimes mixes lifecycle of Fragments added in an Activity.
So I advice you to do mGoogleApiClient
staff in a separate abstract Activity, and make all activities adding Login
fragment extend this Activity.
I have managed to get rid of this crash by creating this abstract Activity below, just copy paste it to your project:
abstract class LoginableActivity : BaseActivity() {
lateinit var googleApiClient: GoogleApiClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.fcm))
.requestEmail()
.build()
googleApiClient = GoogleApiClient.Builder(this)
.enableAutoManage(this, 1, null)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build()
}
override fun onStart() {
super.onStart()
if (!googleApiClient.isConnected && !googleApiClient.isConnecting) {
googleApiClient.connect()
}
}
override fun onStop() {
super.onStop()
if (googleApiClient.isConnected) {
googleApiClient.stopAutoManage(this)
googleApiClient.disconnect()
}
}
}
After you moved googleApiClient
to LoginableActivity
you can access googleApiClient
from Login
fragment like this (let's do it with Java):
final Activity = getActivity();
if (activity instanceof LoginableActivity) {
final GoogleApiClient googleApiClient = ((LoginableActivity) activity).googleApiClient;
}
Solution 10 - Java
Try to use your mGoogleApiClient
from your activity.
if you have declared GoogleApiClient
on your activity, then you can't re-declared on your fragment. instead, re-use variable in that activity from fragment
mGoogleApiClientInFragment = ((Youractivityclass)getActivity()).mGoogleApiClient;
replace YouractivityClass
with your activity of your fragment, and make sure set your mGoogleApiClient
field in your activity to public