For an app I'm working on, which uses People API using credentials (user login). Once the user gives the credentials, I can access various Google APIs, such as People API. An example is one to get a list of contacts:
https://developers.google.com/people/api/rest/v1/people.connections/list
I've noticed that the class com.google.api.client.googleapis.auth.oauth2.GoogleCredential
has become deprecated:
https://googleapis.dev/java/google-api-client/latest/com/google/api/client/googleapis/auth/oauth2/GoogleCredential.html
The app has old code that is based on some old G+ code (here) to reach contacts via the Google account. Here's a snippet of the most important part of it, which causes me trouble of migrating away from it:
object GoogleOuthHelper {
@WorkerThread
fun setUp(context: Context, serverAuthCode: String?): Services {
val httpTransport: HttpTransport = NetHttpTransport()
val jsonFactory = JacksonFactory.getDefaultInstance()
// Redirect URL for web based applications. Can be empty too.
val redirectUrl = "urn:ietf:wg:oauth:2.0:oob"
// Exchange auth code for access token
val tokenResponse = GoogleAuthorizationCodeTokenRequest(
httpTransport, jsonFactory, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET,
serverAuthCode, redirectUrl)
.execute()
// Then, create a GoogleCredential object using the tokens from GoogleTokenResponse
val credential = GoogleCredential.Builder()
.setClientSecrets(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET)
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.build()
val accessToken = tokenResponse.accessToken
getDefaultSecuredSharedPreferences(context).edit()
.putString(SecuredSharedPreferences.KEY__GOOGLE_ACCESS_TOKEN, accessToken).apply()
credential.setFromTokenResponse(tokenResponse)
val appPackageName = context.packageName
val peopleServiceApi = PeopleService.Builder(httpTransport, jsonFactory, credential)
.setApplicationName(appPackageName)
.build()
val peopleService = peopleServiceApi.people()
val otherContactsService = peopleServiceApi.otherContacts()
val contactGroups = peopleServiceApi.contactGroups()
return Services(peopleService, otherContactsService, contactGroups)
}
class Services(
/**https://developers.google.com/people/api/rest/v1/people*/
val peopleService: PeopleService.People,
/**https://developers.google.com/people/api/rest/v1/otherContacts*/
val otherContactsService: OtherContacts,
/**https://developers.google.com/people/api/rest/v1/contactGroups*/
val contactGroups: ContactGroups)
}
The problem is even from the very beginning:
The class GoogleCredentials
doesn't seem to accept anything I got above it for the GoogleCredential
class.
To add more to it, this function takes "serverAuthCode" as parameter, which is from GoogleSignInAccount
, but to get it, I need to use the deprecated GoogleApiClient
class:
fun prepareGoogleApiClient(someContext: Context): GoogleApiClient {
val context = someContext.applicationContext ?: someContext
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestServerAuthCode(GOOGLE_CLIENT_ID)
.requestEmail()
.requestScopes(
Scope(PeopleServiceScopes.CONTACTS_READONLY),
Scope(PeopleServiceScopes.USERINFO_PROFILE),
Scope(PeopleServiceScopes.USER_EMAILS_READ),
Scope(PeopleServiceScopes.CONTACTS),
Scope(PeopleServiceScopes.CONTACTS_OTHER_READONLY)
)
.build()
return GoogleApiClient.Builder(context)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build()
}
And this is what I do with it:
val connectionResult = googleApiClient!!.blockingConnect()
if (!connectionResult.isSuccess)
return
val operation = Auth.GoogleSignInApi.silentSignIn(googleApiClient)
val googleSignInResult: GoogleSignInResult = operation.await()
val googleSignInAccount = googleSignInResult.signInAccount
//use googleSignInAccount.serverAuthCode in setUp() function above
Gradle file has these dependencies:
// https://mvnrepository.com/artifact/com.google.auth/google-auth-library-oauth2-http
implementation 'com.google.auth:google-auth-library-oauth2-http:0.26.0'
// https://github.com/googleapis/google-api-java-client-services/tree/master/clients/google-api-services-people/v1#gradle https://mvnrepository.com/artifact/com.google.apis/google-api-services-people
implementation 'com.google.apis:google-api-services-people:v1-rev20210515-1.31.0'
Other than looking at the docs (and failing to see a resemblance to what I have to handle), I tried to write about this here.
Sadly I can't find how to migrate away from the old code yet.
I tried to ask there how can I migrate (here and here) but didn't get an answer yet.
How can I migrate away from GoogleCredential
to GoogleCredentials
while still using the various APIs such as People API?
In other words: How can I avoid using any of those deprecated classes (GoogleCredential and GoogleApiClient), on Android, while still being able to use the various APIs?
OAuth 2.0 allows users to share specific data with an application while keeping their usernames, passwords, and other information private. For example, an application can use OAuth 2.0 to obtain permission from users to store files in their Google Drives. This OAuth 2.0 flow is called the implicit grant flow.
How can I migrate away from
GoogleCredential
toGoogleCredentials
while still using the various APIs such as People API?In other words: How can I avoid using any of those deprecated classes (GoogleCredential and GoogleApiClient), on Android, while still being able to use the various APIs?
Although you can make GoogleCredentials work directly, it will be better to use a class derived from GoogleCredentials such as UserCredentials which will accommodate token refresh as GoogleCredential does. GoogleCredentials is more of a foundational class.
The following code makes use of UserCredentials. This is mostly what you have presented, but I have changed some credential storing logic for the purpose of the demo. This code has no deprecated methods except startActivityForResult()
.
serverAuthCode
is available from GoogleSignInAccount. Take a look at Moving Past GoogleApiClient on how to remove the dependency on GoogleApiClient. I have update my public gist of RestApiActivity from the Google signin quickstart which shows how to use GoogleOauthHelper as well as GoogleApi.
GoogleOauthHelper.kt
object GoogleOauthHelper {
@WorkerThread
fun setUp(context: Context, serverAuthCode: String?): Services {
val httpTransport: HttpTransport = NetHttpTransport()
val jsonFactory = GsonFactory.getDefaultInstance()
// Redirect URL for web based applications. Can be empty too.
val redirectUrl = "urn:ietf:wg:oauth:2.0:oob"
// Patch for demo
val GOOGLE_CLIENT_ID = context.getString(R.string.GOOGLE_CLIENT_ID)
val GOOGLE_CLIENT_SECRET = context.getString(R.string.GOOGLE_CLIENT_SECRET)
var accessToken: AccessToken? = null
var refreshToken =
getDefaultSecuredSharedPreferences(context).getString(
SecuredSharedPreferences.KEY_GOOGLE_REFRESH_TOKEN,
null
)
if (refreshToken == null) {
/* Did we lose the refresh token, or is this the first time? Refresh tokens are only
returned the first time after the user grants us permission to use the API. So, if
this is the first time doing this, we should get a refresh token. If it's not the
first time, we will not get a refresh token, so we will proceed with the access
token alone. If the access token expires (in about an hour), we will get an error.
What we should do is to ask the user to reauthorize the app and go through the
OAuth flow again to recover a refresh token.
See https://developers.google.com/identity/protocols/oauth2#expiration regarding
how a refresh token can become invalid.
*/
val tokenResponse = GoogleAuthorizationCodeTokenRequest(
httpTransport, jsonFactory, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET,
serverAuthCode, redirectUrl
).execute()
refreshToken = tokenResponse.refreshToken
if (refreshToken != null) {
getDefaultSecuredSharedPreferences(context).edit()
.putString(SecuredSharedPreferences.KEY_GOOGLE_REFRESH_TOKEN, refreshToken)
.apply()
} else {
Log.d("Applog", "No refresh token. Going with access token alone.")
val expiresAtMilliseconds =
Clock.SYSTEM.currentTimeMillis() + tokenResponse.expiresInSeconds * 1000
accessToken = AccessToken(tokenResponse.accessToken, Date(expiresAtMilliseconds))
}
}
Log.d("Applog", "Refresh token: $refreshToken")
// UserCredentials extends GoogleCredentials and permits token refreshing.
val googleCredentials = UserCredentials.newBuilder().run {
clientId = GOOGLE_CLIENT_ID
clientSecret = GOOGLE_CLIENT_SECRET
setRefreshToken(refreshToken)
setAccessToken(accessToken)
build()
}
// Save access token on change
googleCredentials.addChangeListener { oAuth2Credentials ->
saveAccessToken(oAuth2Credentials.accessToken)
}
val requestInitializer: HttpRequestInitializer = HttpCredentialsAdapter(googleCredentials)
val appPackageName = context.packageName
val peopleServiceApi = PeopleService.Builder(httpTransport, jsonFactory, requestInitializer)
.setApplicationName(appPackageName)
.build()
return peopleServiceApi.run { Services(people(), otherContacts(), contactGroups()) }
}
private fun saveAccessToken(accessToken: AccessToken) {
// Persist the token securely.
Log.d("Applog", "Access token has changed: ${accessToken.tokenValue}")
}
// Warning insecure!: Patch for demo.
private fun getDefaultSecuredSharedPreferences(context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}
// Warning insecure!: Patch for demo.
object SecuredSharedPreferences {
const val KEY_GOOGLE_REFRESH_TOKEN = "GOOGLE_REFRESH_TOKEN"
}
class Services(
/**https://developers.google.com/people/api/rest/v1/people*/
val peopleService: PeopleService.People,
/**https://developers.google.com/people/api/rest/v1/otherContacts*/
val otherContactsService: PeopleService.OtherContacts,
/**https://developers.google.com/people/api/rest/v1/contactGroups*/
val contactGroups: PeopleService.ContactGroups,
)
}
I have posted a demo project on GitHub.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With