Here is the complete working example, hope it help for future reader. What the app do is sign in(Sign In API included name and email) first, then request birthday & gender(People API) authentication, and save it to SharedPreferences for reuse on next launch. Finally it will print basic info and advanced (gender & birthday) info.
public class MainActivity extends AppCompatActivity {
static final private int RC_SIGN_IN = 1;
static final private String TAG = "hole";
private WeakReference<MainActivity> weakAct = new WeakReference<>(this);
private GoogleSignInClient mGoogleSignInClient;
private GoogleSignInAccount account;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Scope myScope = new Scope("https://www.googleapis.com/auth/user.birthday.read");
Scope myScope2 = new Scope(Scopes.PLUS_ME);
Scope myScope3 = new Scope(Scopes.PROFILE); //get name and id
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestScopes(myScope, myScope2)
.requestEmail()
.requestProfile()
.build();
mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
account = GoogleSignIn.getLastSignedInAccount(this);
if (account == null) {
reqPerm();
} else {
SharedPreferences sharedPref = getSharedPreferences(account.getId(), MODE_PRIVATE);
if (sharedPref.contains("gender")) {
printBasic();
printAdvanced();
} else {
new GetProfileDetails(account, weakAct, TAG).execute();
}
}
}
private void reqPerm() {
Intent signInIntent = mGoogleSignInClient.getSignInIntent();
startActivityForResult(signInIntent, RC_SIGN_IN);
}
private void printBasic() {
account = GoogleSignIn.getLastSignedInAccount(this);
if (account != null) {
Log.d(TAG, "latest sign in: "
+ "\n\tPhoto url:" + account.getPhotoUrl()
+ "\n\tEmail:" + account.getEmail()
+ "\n\tDisplay name:" + account.getDisplayName()
+ "\n\tFamily(last) name:" + account.getFamilyName()
+ "\n\tGiven(first) name:" + account.getGivenName()
+ "\n\tId:" + account.getId()
+ "\n\tIdToken:" + account.getIdToken()
);
} else {
Log.w(TAG, "basic info is null");
}
}
private void saveAdvanced(Person meProfile) {
account = GoogleSignIn.getLastSignedInAccount(this);
if (account != null) {
SharedPreferences sharedPref = getSharedPreferences(account.getId(), MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
if (n.size() > 0) {
try {
Log.d("hole", "g name: " + n);
editor.putString("givenName", n.get(0).getGivenName());
editor.putString("familyName", n.get(0).getFamilyName());
editor.putString("id", n.get(0).getMetadata().getSource().getId());
} catch (Exception e) {
e.printStackTrace();
//this one should act as fallback priority since it got problem to get name without wait for ~1 minute
// ... when create new account will get empty name
editor.putString("id", account.getId());
editor.putString("givenName", account.getGivenName());
editor.putString("familyName", account.getFamilyName());
}
}
List<Gender> genders = meProfile.getGenders();
if (genders != null && genders.size() > 0) {
String gender = genders.get(0).getValue();
Log.d(TAG, "onPostExecute gender: " + gender);
editor.putString("gender", gender);
} else {
Log.d(TAG, "onPostExecute no gender if set to private ");
editor.putString("gender", ""); //save as main key to know pref saved
}
List<Birthday> birthdays = meProfile.getBirthdays();
if (birthdays != null && birthdays.size() > 0) {
for (Birthday b : birthdays) { //birthday still able to get even private, unlike gender
Date bdate = b.getDate();
if (bdate != null) {
String bday, bmonth, byear;
if (bdate.getDay() != null) bday = bdate.getDay().toString();
else bday = "";
if (bdate.getMonth() != null) bmonth = bdate.getMonth().toString();
else bmonth = "";
if (bdate.getYear() != null) byear = bdate.getYear().toString();
else byear = "";
editor.putString("bday", bday);
editor.putString("bmonth", bmonth);
editor.putString("byear", byear);
}
}
} else {
Log.w(TAG, "saveAdvanced no birthday");
}
editor.commit(); //next instruction is print from pref, so don't use apply()
} else {
Log.w(TAG, "saveAdvanced no acc");
}
}
private void printAdvanced() {
account = GoogleSignIn.getLastSignedInAccount(this);
if (account != null) {
SharedPreferences sharedPref = getSharedPreferences(account.getId(), MODE_PRIVATE);
if (sharedPref.contains("gender")) { //this checking works since null still saved
String gender = sharedPref.getString("gender", "");
Log.d(TAG, "gender: " + gender);
if (sharedPref.contains("bday")) { //this checking works since null still saved
String bday = sharedPref.getString("bday", "");
String bmonth = sharedPref.getString("bmonth", "");
String byear = sharedPref.getString("byear", "");
Log.d(TAG, bday + "/" + bmonth + "/" + byear);
} else {
Log.w(TAG, "failed ot get birthday from pref");
}
String givenName = sharedPref.getString("givenName", "");
String familyName = sharedPref.getString("familyName", "");
String id = sharedPref.getString("id", "");
} else {
Log.w(TAG, "failed ot get data from pref -2");
}
} else {
Log.w(TAG, "failed ot get data from pref -1");
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
if (resultCode == Activity.RESULT_OK) {
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
handleSignInResult(task);
} else {
Log.w(TAG, "failed, user denied OR no network OR jks SHA1 not configure yet at play console android project");
}
}
}
private void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
try {
GoogleSignInAccount account = completedTask.getResult(ApiException.class);
// Signed in successfully, show authenticated UI.
new GetProfileDetails(account, weakAct, TAG).execute();
} catch (ApiException e) { //cancel choose acc will come here with status code 12501 if not check RESULT_OK
// , more status code at:
//https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInStatusCodes
Log.w(TAG, "signInResult:failed code=" + e.getStatusCode());
}
}
static class GetProfileDetails extends AsyncTask<Void, Void, Person> {
private PeopleService ps;
private int authError = -1;
private WeakReference<MainActivity> weakAct;
private String TAG;
GetProfileDetails(GoogleSignInAccount account, WeakReference<MainActivity> weakAct, String TAG) {
this.TAG = TAG;
this.weakAct = weakAct;
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(
this.weakAct.get(), Collections.singleton(Scopes.PROFILE));
credential.setSelectedAccount(
new Account(account.getEmail(), "com.google"));
HttpTransport HTTP_TRANSPORT = AndroidHttp.newCompatibleTransport();
JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
ps = new PeopleService.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName("Google Sign In Quickstart")
.build();
}
@Override
protected Person doInBackground(Void... params) {
Person meProfile = null;
try {
meProfile = ps
.people()
.get("people/me")
.setPersonFields("names,genders,birthdays")
.execute();
} catch (UserRecoverableAuthIOException e) {
e.printStackTrace();
authError = 0;
} catch (GoogleJsonResponseException e) {
e.printStackTrace();
authError = 1;
} catch (IOException e) {
e.printStackTrace();
authError = 2;
}
return meProfile;
}
@Override
protected void onPostExecute(Person meProfile) {
MainActivity mainAct = weakAct.get();
if (mainAct != null) {
mainAct.printBasic();
if (authError == 0) { //app has been revoke, re-authenticated required.
mainAct.reqPerm();
} else if (authError == 1) {
Log.w(TAG, "People API might not enable at" +
" https://console.developers.google.com/apis/library/people.googleapis.com/?project=<project name>");
} else if (authError == 2) {
Log.w(TAG, "API io error");
} else {
if (meProfile != null) {
mainAct.saveAdvanced(meProfile);
mainAct.printAdvanced();
}
}
}
}
}
}
Reminder:
- Add
<uses-permission android:name="android.permission.INTERNET" /> in AndroidManifest.xml.
- Add
implementation 'com.google.android.gms:play-services-auth:12.0.1', implementation 'com.google.apis:google-api-services-people:v1-rev255-1.23.0', and implementation 'com.google.api-client:google-api-client-android:1.23.0' in dependencies {} of build.gradle.
- In my case I downgrade
compileSdkVersion, targetSdkVersion, and appcompat-v7 from 27 to 26 since I got warning after #2 dependencies added.
- Add
signingConfigs {
debug {
storeFile file('<path to jks file>')
keyAlias '<your key alias>'
keyPassword '<your key password>'
storePassword '<your store password>'
}
}
in build.gradle, which jks file generated from Build -> Generated Signed APK... -> Create new...
keytool -exportcert -keystore <path to jks file> -list -v to get SHA1 hex key, then visits play console, fill in project name, app package name, SHA1 hex key.
- Enable People API at https://console.developers.google.com/apis/library/people.googleapis.com/?project=[your project id]", which project id can get from play console. Note that it's not project name.
- I noticed no such
Scopes.BIRTHDAY in library, so I have to hard-coded the birthday endpoint URL"https://www.googleapis.com/auth/user.birthday.read", which the link can get from https://developers.google.com/people/v1/how-tos/authorizing#profile-scopes OR "Show Scopes" in "Try it API" panel at https://developers.google.com/people/api/rest/v1/people/get
- Birthday is a list, and it may loop 2 date items, one item lack of year in my case. My code always replace to save those 2 items. There may be better way to handle it.
- Gender only able to return if it's not private. Birthday no such restriction.
- Since Android device need ~1 minute delay to get new created account name, so you might need to use PROFILE scope instead of simple
account.getGivenName() and account.getFamilyName().