Android Google Photos API via Java Library (Failed)

I didn't proceed with this approach due to the following reasons

  • OAuth Secret will be exposed on Android, which is a security risk
  • google-photos-library-client uses protobuf-java which conflict protobuf-lite used by Firestore, and excluding either one causes issues with its respective library. This is officially a Java library (not Android library).

In the end, I use a Python Cloud Functions to access the Google Photos API instead.

The following is some code snippets which I try on, but never successfully tested them as I couldn't proceed further due to the protobuf library conflict with Firestore.

Enable Google Photos API

https://console.cloud.google.com/apis/library/photoslibrary.googleapis.com

OAuth Client Id and Secret

Goto Google Cloud Console -> Credentials

Create credentials -> OAuth Client Id

  • Application Type: Android
  • Name
  • SHA-1
  • Package Name

Download JSON and save to /app/src/main/assets/oauth2-secret.json

Module:app build.gradle

android {
    // Duplicate files copied in APK META-INF/* caused by google-photos-library-client
    packagingOptions {
        exclude 'META-INF/DEPENDENCIES'
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/LICENSE.txt'
        exclude 'META-INF/license.txt'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/NOTICE.txt'
        exclude 'META-INF/notice.txt'
        exclude 'META-INF/ASL2.0'

        // possible alternative solutions
        // pickFirst  'META-INF/*'
    }

    // OPTIONAL: conflict with google-photos-library-client
    /*
    configurations {
        // implementation.exclude module:'proto-google-common-protos'
        implementation.exclude module:'protolite-well-known-types'
        // exclude 'protobuf-lite' will cause firestore "com.google.protobuf.Timestamp but expected Reference" error
        implementation.exclude module:'protobuf-lite'
    }
     */
}

dependencies {
    // to enable GoogleAccountCredential.usingOAuth2
    implementation('com.google.api-client:google-api-client-android:1.23.0') {
        exclude group: 'org.apache.httpcomponents'
        exclude module: 'guava-jdk5'
    }

    implementation('com.google.photos.library:google-photos-library-client:1.4.0') {
        // exclude module: 'proto-google-common-protos'
        // if exclude 'protobuf-java', unresolved supertypes: com.google.protobuf.GeneratedMessageV3
        exclude module: 'protobuf-java'
        exclude module: 'threetenbp'
    }
}

Code

I didn't get the chance to test if the following code works without error (use it only for reference)

class TestFragment : Fragment() {    companion object {        fun newInstance() = TestFragment()        private const val REQUEST_GOOGLE_PHOTOS_SIGN_IN = 1    }    fun signIn() {        val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)            .requestScopes(Scope("https://www.googleapis.com/auth/photoslibrary.readonly"))            .build()        val client = GoogleSignIn.getClient(context!!, signInOptions)        startActivityForResult(client.signInIntent, REQUEST_GOOGLE_PHOTOS_SIGN_IN)    }    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {        super.onActivityResult(requestCode, resultCode, data)         if (requestCode == REQUEST_GOOGLE_PHOTOS_SIGN_IN) {            if (resultCode == RESULT_OK) {                GoogleSignIn.getSignedInAccountFromIntent(data)                    .addOnSuccessListener { account ->                        val scopes = listOf("https://www.googleapis.com/auth/photoslibrary.readonly")                        val credential = GoogleAccountCredential.usingOAuth2(context, scopes)                        credential.selectedAccount = account.account                        val clientSecrets = GoogleClientSecrets.load(                             jsonFactory, InputStreamReader(                                context!!.assets.open("oauth2-secret.json")                            )                        )                        val clientId = clientSecrets.details.clientId                        val clientSecret = clientSecrets.details.clientSecret                        val userCredentials = UserCredentials.newBuilder()                            .setClientId(clientId)                            .setClientSecret(clientSecret)                            .setAccessToken(credential.token) // AccessToken(credential.token, null)                            .build()                        val settings = PhotosLibrarySettings.newBuilder()                            .setCredentialsProvider(                                FixedCredentialsProvider.create(userCredentials)                            )                            .build()                        val client = PhotosLibraryClient.initialize(settings)                        for (album in client.listAlbums().iterateAll()) {                            Timber.d("Album: ${album.title}")                        }                    }            }         }    }}

References:

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.