Question

Since Android 4.2.2 it's possible to run Google Services on the Android Emulator. I'm currently making an Android app and made a test project to see if I can get Google+ sign-in and sign-out to work.

I've followed the following tutorial: http://www.androidhive.info/2014/02/android-login-with-google-plus-account-1/

With extra info used from the following tutorials/sites:

This generated the following code:

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testproject_gmaillogin"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="9"
        android:targetSdkVersion="19" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.USE_CREDENTIALS" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >

        <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />

        <activity
            android:name="com.example.testproject_gmaillogin.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">TestProject_GmailLogin</string>
    <string name="action_settings">Settings</string>

    <string name="profile_pic_description">Google Profile Picture</string>
    <string name="btn_logout_from_google">Logout from Google</string>
    <string name="btn_revoke_access">Revoke Access</string>

</resources>

activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity" >

    <LinearLayout
        android:id="@+id/profile_layout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:orientation="horizontal"
        android:weightSum="3"
        android:visibility="gone">

        <ImageView
            android:id="@+id/img_profile_pic"
            android:contentDescription="@string/profile_pic_description"
            android:layout_width="80dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:orientation="vertical"
            android:layout_weight="2" >

            <TextView
                android:id="@+id/txt_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="5dp"
                android:textSize="20sp" />

            <TextView
                android:id="@+id/txt_email"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="5dp"
                android:textSize="18sp" />
        </LinearLayout>
    </LinearLayout>

    <com.google.android.gms.common.SignInButton
        android:id="@+id/btn_sign_in"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"/>

    <Button
        android:id="@+id/btn_sign_out"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/btn_logout_from_google"
        android:visibility="gone"
        android:layout_marginBottom="10dp"/>

    <Button
        android:id="@+id/btn_revoke_access"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/btn_revoke_access"
        android:visibility="gone" />

</LinearLayout>

MainActivity.java:

package com.example.testproject_gmaillogin;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.SignInButton;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.plus.Plus;
import com.google.android.gms.plus.model.people.Person;

import android.support.v7.app.ActionBarActivity;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends ActionBarActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnClickListener
{
    // Logcat tag
    private static final String TAG = "MainActivity";

    // Profile pix image size in pixels
    private static final int PROFILE_PIC_SIZE = 400;

    // Request code used to invoke sign in user interactions
    private static final int RC_SIGN_IN = 0;

    // Client used to interact with Google APIs
    private GoogleApiClient mGoogleApiClient;

    // A flag indicating that a PendingIntent is in progress and prevents
    // us from starting further intents
    private boolean mIntentInProgress;

    // Track whether the sign-in button has been clicked so that we know to resolve
    // all issues preventing sign-in without waiting
    private boolean mSignInClicked;

    // Store the connection result from onConnectionFailed callbacks so that we can
    // resolve them when the user clicks sign-in
    private ConnectionResult mConnectionResult;

    // The used UI-elements
    private SignInButton btnSignIn;
    private Button btnSignOut, btnRevokeAccess;
    private ImageView imgProfilePic;
    private TextView txtName, txtEmail;
    private LinearLayout profileLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Get the UI-elements
        btnSignIn = (SignInButton) findViewById(R.id.btn_sign_in);
        btnSignOut = (Button) findViewById(R.id.btn_sign_out);
        btnRevokeAccess = (Button) findViewById(R.id.btn_revoke_access);
        imgProfilePic = (ImageView) findViewById(R.id.img_profile_pic);
        txtName = (TextView) findViewById(R.id.txt_name);
        txtEmail = (TextView) findViewById(R.id.txt_email);
        profileLayout = (LinearLayout) findViewById(R.id.profile_layout);

        // Set the Button onClick-listeners
        btnSignIn.setOnClickListener(this);
        btnSignOut.setOnClickListener(this);
        btnRevokeAccess.setOnClickListener(this);

        mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .addApi(Plus.API, null)
            .addScope(Plus.SCOPE_PLUS_LOGIN)
            .build();
    }

    @Override
    protected void onStart(){
        super.onStart();
        mGoogleApiClient.connect();
    }

    @Override
    protected void onStop(){
        super.onStop();

        if(mGoogleApiClient.isConnected())
            mGoogleApiClient.disconnect();
    }

    @Override
    public void onClick(View view){
        switch(view.getId()){
            case R.id.btn_sign_in:
                signInWithGPlus();
                break;
            case R.id.btn_sign_out:
                signOutFromGPlus();
                break;
            case R.id.btn_revoke_access:
                revokeGPlusAccess();
                break;
        }
    }

    @Override
    public void onConnectionFailed(ConnectionResult result) {
        if(!result.hasResolution()){
            GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), this, 0).show();
            return;
        }

        if(!mIntentInProgress){
            // Store the ConnectionResult so that we can use it later when the user clicks 'sign-in'
            mConnectionResult = result;

            if(mSignInClicked)
                // The user has already clicked 'sign-in' so we attempt to resolve all
                // errors until the user is signed in, or they cancel
                resolveSignInErrors();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int responseCode, Intent intent){
        if(requestCode == RC_SIGN_IN && responseCode == RESULT_OK)
            SignInClicked = true;

            mIntentInProgress = false;

            if(!mGoogleApiClient.isConnecting())
                mGoogleApiClient.connect();
        }
    }

    @Override
    public void onConnected(Bundle connectionHint) {
        mSignInClicked = false;
        Toast.makeText(this, "User is connected!", Toast.LENGTH_LONG).show();

        // Get all the user's information
        getProfileInformation();

        // Update the UI after sign-in
        updateUI(true);
    }

    @Override
    public void onConnectionSuspended(int cause){
        mGoogleApiClient.connect();
        updateUI(false);
    }

    // Updating the UI, showing/hiding buttons and profile layout
    private void updateUI(boolean isSignedIn){
        if(isSignedIn){
            btnSignIn.setVisibility(View.GONE);
            btnSignOut.setVisibility(View.VISIBLE);
            btnRevokeAccess.setVisibility(View.VISIBLE);
            profileLayout.setVisibility(View.VISIBLE);
        }
        else{
            btnSignIn.setVisibility(View.VISIBLE);
            btnSignOut.setVisibility(View.GONE);
            btnRevokeAccess.setVisibility(View.GONE);
            profileLayout.setVisibility(View.GONE);
        }
    }

    // Sign-in into Google
    private void signInWithGPlus(){
        if(!mGoogleApiClient.isConnecting()){
            mSignInClicked = true;
            resolveSignInErrors();
        }
    }

    // Method to resolve any sign-in errors
    private void resolveSignInErrors(){
        if(mConnectionResult.hasResolution()){
            try{
                mIntentInProgress = true;

                //Toast.makeText(this, "Resolving Sign-in Errors", Toast.LENGTH_SHORT).show();

                mConnectionResult.startResolutionForResult(this, RC_SIGN_IN);
            }
            catch(SendIntentException e){
                // The intent was cancelled before it was sent. Return to the default
                // state and attempt to connect to get an updated ConnectionResult
                mIntentInProgress = false;
                mGoogleApiClient.connect();
            }
        }
    }

    // Fetching the user's infromation name, email, profile pic
    private void getProfileInformation(){
        try{
            if(Plus.PeopleApi.getCurrentPerson(mGoogleApiClient) != null){
                Person currentPerson = Plus.PeopleApi.getCurrentPerson(mGoogleApiClient);
                String personName = currentPerson.getDisplayName();
                String personPhotoUrl = currentPerson.getImage().getUrl();
                String personGooglePlusProfile = currentPerson.getUrl();
                String personEmail = Plus.AccountApi.getAccountName(mGoogleApiClient);

                Log.e(TAG, "Name: " + personName + ", "
                        + "plusProfile: " + personGooglePlusProfile + ", "
                        + "email: " + personEmail + ", "
                        + "image: " + personPhotoUrl);

                txtName.setText(personName);
                txtEmail.setText(personEmail);

                // by default the profile url gives 50x50 px image,
                // but we can replace the value with whatever dimension we
                // want by replacing sz=X
                personPhotoUrl = personPhotoUrl.substring(0, personPhotoUrl.length() - 2)
                        + PROFILE_PIC_SIZE;

                new LoadProfileImage(imgProfilePic).execute(personPhotoUrl);
            }
            else{
                Toast.makeText(getApplicationContext(), "Person information is null", Toast.LENGTH_LONG).show();
            }
        }
        catch(Exception ex){
            ex.printStackTrace();
        }
    }

    // Sign-out from Google
    private void signOutFromGPlus(){
        if(mGoogleApiClient.isConnected()){
            Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
            mGoogleApiClient.disconnect();
            mGoogleApiClient.connect();
            updateUI(false);
        }
    }

    // Revoking access from Google
    private void revokeGPlusAccess(){
        if(mGoogleApiClient.isConnected()){
            Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
            Plus.AccountApi.revokeAccessAndDisconnect(mGoogleApiClient)
                .setResultCallback(new ResultCallback<Status>(){
                    @Override
                    public void onResult(Status s){
                        Log.e(TAG, "User access revoked!");
                        mGoogleApiClient.connect();
                        updateUI(false);
                    }
                });
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings)
            return true;

        return super.onOptionsItemSelected(item);
    }
}

LoadProfileImage.java:

package com.example.testproject_gmaillogin;

import java.io.InputStream;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;

/**
 * Background async task to load user profile picture from url
 **/
public class LoadProfileImage extends AsyncTask<String, Void, Bitmap> {
    private ImageView bmImage;

    public LoadProfileImage(ImageView bmImage){
        this.bmImage = bmImage;
    }

    @Override
    protected Bitmap doInBackground(String... urls){
        String urlDisplay = urls[0];
        Bitmap mIcon11 = null;
        try{
            InputStream in = new java.net.URL(urlDisplay).openStream();
            mIcon11 = BitmapFactory.decodeStream(in);
        }
        catch(Exception ex){
            Log.e("Error", ex.getMessage());
            ex.printStackTrace();
        }
        return mIcon11;
    }

    @Override
    protected void onPostExecute(Bitmap result){
        bmImage.setImageBitmap(result);
    }
}

The other steps I did were:

At https://console.developers.google.com/project I've created a project with:

Google+ API on:

Google+ API on

And a Client ID created with the correct SHA1 and exact same namespace as the project:

And a Client ID created with the correct SHA1

At Eclipse:

I've installed the google-play-services library:

Google-play services installed

And added it to the project:

Google-play services library added (2) Google-play services library added (2)

I've also created an Emulator with version 4.4.2:

I

But when I run the app I get the following error, and this error keeps popping up when I click on the button:

Get Google Play services Error

Anyone has any idea where it goes wrong? Thanks in advance for the responses.

Was it helpful?

Solution

Ok, after trying some things it turned out I had one last option not correctly checked, which wasn't mentioned anywhere in the tutorial(s)..

Instead of Android 4.4.2 as Project Build Target & Emulator Target, I've selected Google APIs 4.4.2. Now I don't get the error of Google Play services anymore.

I do get a NullPointerException though, but at least I can continue.. ;)

EDIT: Fixed the NullPointerException and modified the code in my original post. Everything works as it should now and I hope this post helps other people with the same (or other) errors using google play services sign-in using an Android Emulator.

Solution Error Solution Error Emulator

OTHER TIPS

Try using the Goole APIs API level 19 as emulator target instead of normal API level 19.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top