Integrating Google Drive in Android
See how to integrate the Google Drive SDK in order to let your users access and download their Drive files directly to your app. By Kevin D Moore.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Integrating Google Drive in Android
25 mins
- Getting Started
- Registering for Google Drive
- Building Your Android App
- Updating the Gradle File
- Modifying the Android Manifest
- Creating a File Provider
- Adding Strings
- Updating the UI
- Creating a ServiceListener Interface
- Creating a Data Class: GoogleDriveConfig
- Creating the GoogleDriveService
- Handling Activity Results
- Opening a Picked-File Dialog
- Logging In and Out
- Updating MainActivity
- Handling the OnActivityResult Method
- Where to Go From Here?
Adding Strings
Now, you’ll add the strings that you’ll need for the UI. Open the strings.xml file and add:
<string name="source_google_drive">Google Drive</string>
<string name="start_drive">Start Google Drive</string>
<string name="login">Log In</string>
<string name="logout">Log Out</string>
<string name="status_logged_out">Logged Out</string>
<string name="status_logged_in">Logged In</string>
<string name="status_user_cancelled">User Cancelled</string>
<string name="status_error">We found a problem: %1$s</string>
<string name="not_open_file">Could not open file</string>
The first string is for the Google Drive’s activity title, and the rest are for the UI.
Updating the UI
Next, you’ll update the UI. To do so, you’ll simply create three buttons to Login, Logout, and Open Google Drive, and a TextView
to display login status. Open activity_main.xml and replace the contents with the following:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/login"
app:layout_constraintBottom_toTopOf="@+id/start"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/start_drive"
app:layout_constraintBottom_toTopOf="@+id/logout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login" />
<Button
android:id="@+id/logout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/logout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/start" />
<TextView
android:id="@+id/status"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:gravity="center_horizontal"
android:text="@string/status_logged_out"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>
Run the app and make sure the UI is displayed correctly:
If everything works correctly, you should have a basic UI with three buttons and a status message at the bottom. If the project does not compile or something goes wrong when running, compare your work with each of the steps above.
Creating a ServiceListener Interface
Since there are only a few classes, you will put all of them in the root source folder. Start with the interface that the listener of your service must implement. Create a new Kotlin interface named ServiceListener:
interface ServiceListener {
fun loggedIn() //1
fun fileDownloaded(file: File) //2
fun cancelled() //3
fun handleError(exception: Exception) //4
}
You may need to choose Option+Return on macOS Alt+Enter on PC to pull in the import for the File
class.
These methods notify the listener when:
-
loggedIn()
: A user is successfully authenticated. -
fileDownloaded(file: File)
: A file is selected and downloaded successfully. -
cancelled()
: A login or file selection is cancelled. -
handleError(exception: Exception)
: There is any error.
This interface will be implemented by MainActivity and used by a service as a way to let the user of the service know when something has happened.
Creating a Data Class: GoogleDriveConfig
Next, create a simple data class for holding the information that the service needs. Create a new data class named GoogleDriveConfig:
data class GoogleDriveConfig(val activityTitle: String? = null, val mimeTypes: List<String>? = null)
This class contains the title that Google Drive will designate as the activity’s title and the mimeTypes that determines which file types to show.
Creating the GoogleDriveService
Next, you’ll create the actual service. Create a new class named GoogleDriveService:
class GoogleDriveService(private val activity: Activity, private val config: GoogleDriveConfig) {
}
The class is not an Android Service, but instead acts as a service for MainActivity. You will be adding the following code, in order.
First, add a companion object:
companion object {
private val SCOPES = setOf<Scope>(Drive.SCOPE_FILE, Drive.SCOPE_APPFOLDER)
val documentMimeTypes = arrayListOf(
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document")
const val REQUEST_CODE_OPEN_ITEM = 100
const val REQUEST_CODE_SIGN_IN = 101
const val TAG = "GoogleDriveService"
}
Scopes are Google Drive’s set of permissions. Therefore, by giving a file and an app folder scope, you tell Google Drive to let you handle files and folders.
The mime types are for the type of files you want to allow the user to pick. If you want the user to choose images, you would use image/*
. Here, you pick .pdf and .doc/.docx files.
You also have two request codes to use for handling the result of signing in and picking a file. The TAG
constant is used for Logging.
After the companion object section, add the following variables:
var serviceListener: ServiceListener? = null //1
private var driveClient: DriveClient? = null //2
private var driveResourceClient: DriveResourceClient? = null //3
private var signInAccount: GoogleSignInAccount? = null //4
These are:
-
serviceListener
is the listener of your service. -
driveClient
handles high-level drive functions like Create File, Open File, and Sync. -
driveResourceClient
handles access to Drive resources and/or files. -
signInAccount
keeps track of the currently signed-in account.
Now add a GoogleSignInClient property that is lazily-initialized:
private val googleSignInClient: GoogleSignInClient by lazy {
val builder = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
for (scope in SCOPES) {
builder.requestScopes(scope)
}
val signInOptions = builder.build()
GoogleSignIn.getClient(activity, signInOptions)
}
googleSignInClient
is created when needed and includes the scopes defined earlier. The last statement returns the GoogleSignInClient
.
Handling Activity Results
You need to be able to handle the results from the user who is signing in and picking a file in the MainActivity. Create a method named onActivityResult
, which will be called inside onActivityResult
of MainActivity:
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_CODE_SIGN_IN -> {
if (data != null) {
handleSignIn(data)
} else {
serviceListener?.cancelled()
}
}
REQUEST_CODE_OPEN_ITEM -> {
if (data != null) {
openItem(data)
} else {
serviceListener?.cancelled()
}
}
}
}
In the method, you call helper methods or the serviceListener
depending on the requestCode
. You can check the result against the presence of data
instead of resultCode
. If no data is returned, it means the user cancelled the action.
Now add the helper method for handling sign in with another method to initialize the drive client:
private fun handleSignIn(data: Intent) {
val getAccountTask = GoogleSignIn.getSignedInAccountFromIntent(data)
if (getAccountTask.isSuccessful) {
initializeDriveClient(getAccountTask.result)
} else {
serviceListener?.handleError(Exception("Sign-in failed.", getAccountTask.exception))
}
}
private fun initializeDriveClient(signInAccount: GoogleSignInAccount) {
driveClient = Drive.getDriveClient(activity.applicationContext, signInAccount)
driveResourceClient = Drive.getDriveResourceClient(activity.applicationContext, signInAccount)
serviceListener?.loggedIn()
}
Once the user has signed in, you handle the result in initializeDriveClient()
. This will create your drive clients. It also notifies the listener that the user has successfully signed in.
After a user has picked a file, you will get an activity intent and pass it to openItem()
, so add that helper method now:
private fun openItem(data: Intent) {
val driveId = data.getParcelableExtra<DriveId>(OpenFileActivityOptions.EXTRA_RESPONSE_DRIVE_ID)
downloadFile(driveId)
}
This function gets the driveId
from the intent options and passes that ID to another helper method downloadFile()
.
The key aspect of the whole service is downloading the picked file. To do that, you need to get an input stream to the file and save it to a local file. You will use Square’s Okio library to easily take that stream and save it to a file.
Add the downloadFile()
method now:
private fun downloadFile(data: DriveId?) {
if (data == null) {
Log.e(TAG, "downloadFile data is null")
return
}
val drive = data.asDriveFile()
var fileName = "test"
driveResourceClient?.getMetadata(drive)?.addOnSuccessListener {
fileName = it.originalFilename
}
val openFileTask = driveResourceClient?.openFile(drive, DriveFile.MODE_READ_ONLY)
openFileTask?.continueWithTask { task ->
val contents = task.result
contents.inputStream.use {
try {
//This is the app's download directory, not the phones
val storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
val tempFile = File(storageDir, fileName)
tempFile.createNewFile()
val sink = Okio.buffer(Okio.sink(tempFile))
sink.writeAll(Okio.source(it))
sink.close()
serviceListener?.fileDownloaded(tempFile)
} catch (e: IOException) {
Log.e(TAG, "Problems saving file", e)
serviceListener?.handleError(e)
}
}
driveResourceClient?.discardContents(contents)
}?.addOnFailureListener { e ->
// Handle failure
Log.e(TAG, "Unable to read contents", e)
serviceListener?.handleError(e)
}
}
There’s a lot going on in this method. Notice the getMetaData()
call. That is needed to get the name of the chosen file. You are then saving the file to your app’s internal download folder (which is not visible to the user), then alerting the listener about the downloaded file and where to find it.