Using Google Drive in Your App

      13 Comments on Using Google Drive in Your App

In this article we'll walk you through the setup procedure and cover some basic scenarios on how to use Google Drive in your apps. Let's get started.You probably know what Google Drive is, just in case you don’t… it’s a cloud bases storage. In this article we’ll walk you through the setup procedure and cover some basic scenarios on how to use Google Drive in your apps. Let’s get started.

Pre-flight

Before you can use Google Drive, we’ll have to spend some time setting it up. Generally speaking, Google has great help pages and tutorials, but I had to scratch my head a couple of times when trying to make Google Drive work. You might have the same problems trying to set up API access for your project and in general, the code on Google is out of date and doesn’t include swift examples.

API Access

Before doing anything, we’ll create a project on google cloud platform and enable Google Drive API access (this is enabled by default, btw). So, head to the google cloud platform website, you should see your existing projects (if you have any) in the upper left corner:

If you click on the drop-down menu, you’ll be able to create a new project:

Selecting the ‘plus’ button will lead you to a new screen where you can choose a name for your new project:

When you create your new project, your Google Drive API should be enabled by default and you should be presented with a screen similar to this one:

In case this didn’t happen for you, you can always enable the Google Drive manually. Select the menu icon in the top left, select ‘API & Services’ and ‘Library’ from it:

If you search for ‘Google Drive’ you’ll see it in the list, simply enable it from there:

Now we need to enable the API…

Enable API

Technically you should be able to do this step directly from the quick start guide, but it didn’t work for me. I had to go through the above steps to create a project on the cloud console and enable the API.

With the above steps finished, go to the iOS quick start guide on google and select the ‘enable the API’ button:

You will be presented with a simple wizard where you will have to configure your OAuth client. You will need your bundle ID for this step. Select your project from the dropdown list:

Give it a nice name. Bear in mind that this name will be presented to your users when your app asks for permissions:

After that select the platform and put in your bundle id:

You’ll be taken to the last step where you can download your credentials that will contain the keys necessary for setting up your Xcode project:

Next, we need to set up the project.

Project Setup

The only thing you need to do is set the custom URL scheme. Select your target, go to ‘Info’ tab, and add a new URL scheme under ‘URL Types’ section:

You will find your reversed client id in the client configuration plist file that you downloaded previously.

That’s it for setting everything up. Time to play in code 🙂

The Code

Google Drive REST API wrapper library is available on cocoapods, so it’s real easy to get started, go and update your ‘Podfile’ with the following:

pod 'GoogleAPIClientForREST/Drive', '~> 1.2.1'
pod 'GoogleSignIn', '~> 4.1.1'

Run pod install from the terminal, and open you workspace. One last thing to do is to initialise the SDK. Initialise the SDK from your AppDelegate, within ‘didFinishLaunching…’ function:

GIDSignIn.sharedInstance().clientID = "CLIENT_ID"

You can find the ‘CLIENT_ID’ in the credentials.plist file that you downloaded from the quick start guide.

We created a custom URL scheme for our app, which means we’ll have to handle incoming URLs. While we’re in the AppDelegate, let’s finish this:

func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
    return GIDSignIn.sharedInstance().handle(url, sourceApplication: sourceApplication, annotation: annotation)
}
    
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    let sourceApplication = options[UIApplicationOpenURLOptionsKey.sourceApplication] as? String
    let annotation = options[UIApplicationOpenURLOptionsKey.annotation]
    return GIDSignIn.sharedInstance().handle(url, sourceApplication: sourceApplication, annotation: annotation)
}

Sign In

Obviously, your users will need to sign in to their google account in order to use Google Drive. And we’ll need to set the permissions levels. We can do all of that in a short function:

fileprivate let service = GTLRDriveService()
private func setupGoogleSignIn() {
    GIDSignIn.sharedInstance().delegate = self
    GIDSignIn.sharedInstance().uiDelegate = self
    GIDSignIn.sharedInstance().scopes = [kGTLRAuthScopeDriveFile]
    GIDSignIn.sharedInstance().signInSilently()
}

We keep an instance variable to the Google Drive service, we’ll need it later. The only interesting parts of this function is the ‘scopes’ that we’re setting. In our example we’re using a scope of ‘DriveFile’ this means that your app will have read/write permissions on the files/folders it creates. This is exactly what we need. ‘signInSilently()’ will try to sign the user using the previous session’s token.

Once the sign in is complete, we have to save the ‘authorizer’ (assign it to our service’s ‘authorizer’ variable), which is a small class that the SDK will use to authorise all your requests:

// MARK: - GIDSignInDelegate
extension ViewController: GIDSignInDelegate {
    func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
        if let _ = error {
            service.authorizer = nil
        } else {
            service.authorizer = user.authentication.fetcherAuthorizer()
        }
    }
}

Now we’ll create a small class that we’ll use to manage files on Google Drive.

Our Google Drive Wrapper

We’ll create a small wrapper around Google Drive REST API and pass in the Google Drive Service that we saved in previously:

private let service: GTLRDriveService
    
init(_ service: GTLRDriveService) {
    self.service = service
}

Now we’ll create methods for listing files, uploading and deleting.

Listing Files

Before writing other functions, we’ll create a small utility function that will allow us to get object IDs. Both, files and folders have object IDs and you’ll need these for pretty much everything you do with Google Drive. We’ll need a function for searching, like so:

public func search(_ fileName: String, onCompleted: @escaping (String?, Error?) -> ()) {
    let query = GTLRDriveQuery_FilesList.query()
    query.pageSize = 1
    query.q = "name contains '\(fileName)'"
        
    service.executeQuery(query) { (ticket, results, error) in
        onCompleted((results as? GTLRDrive_FileList)?.files?.first?.identifier, error)
    }
}

We are creating a simple query here that is returning the first object it finds. We’ll use this when we search for our folder that we’ll be using for uploading.

Now we can move on to listing files. We’ll use two functions for this:

public func listFilesInFolder(_ folder: String, onCompleted: @escaping (GTLRDrive_FileList?, Error?) -> ()) {
    search(folder) { (folderID, error) in
        guard let ID = folderID else {
            onCompleted(nil, error)
            return
        }
        self.listFiles(ID, onCompleted: onCompleted)
    }
}
    
private func listFiles(_ folderID: String, onCompleted: @escaping (GTLRDrive_FileList?, Error?) -> ()) {
    let query = GTLRDriveQuery_FilesList.query()
    query.pageSize = 100
    query.q = "'\(folderID)' in parents"
        
    service.executeQuery(query) { (ticket, result, error) in
        onCompleted(result as? GTLRDrive_FileList, error)
    }
}

Our ‘listFilesInFolder’ function will try to search for a folder in order to get the object ID. In the second method we’ll be using this ID to search for files that have this ID set as one of the parents (essentially, a folder). And that’s it for listing the files.

This function will give us an ‘GTLRDrive_FileList’ object, which is a collection of files. Naturally, this array won’t contain the actual files, just some info about the files. If we want to download the file to our device we need another function. Fortunately, this is a simple one:

public func download(_ fileID: String, onCompleted: @escaping (Data?, Error?) -> ()) {
    let query = GTLRDriveQuery_FilesGet.queryForMedia(withFileId: fileID)
    service.executeQuery(query) { (ticket, file, error) in
        onCompleted((file as? GTLRDataObject)?.data, error)
    }
}

We only need to pass in the file id and execute the query. In the closure we’ll get the data. Now, let’s check out how to upload files.

Uploading Files

We’ll be uploading all our files in one folder, so to keep things clean on the user’s Google Drive. We’ll need a utility function that will enable us to create folders:

public func createFolder(_ name: String, onCompleted: @escaping (String?, Error?) -> ()) {
    let file = GTLRDrive_File()
    file.name = name
    file.mimeType = "application/vnd.google-apps.folder"
        
    let query = GTLRDriveQuery_FilesCreate.query(withObject: file, uploadParameters: nil)
    query.fields = "id"
        
    service.executeQuery(query) { (ticket, folder, error) in
        onCompleted((folder as? GTLRDrive_File)?.identifier, error)
    }
}

Google Drive treats folders just like files, they only have a special MIME type.

Now we can work on our upload functions. We’ll use two functions, one will be a public function that will simplify the interface, and another one will actually upload the file:

public func uploadFile(_ folderName: String, filePath: String, MIMEType: String, onCompleted: ((String?, Error?) -> ())?) {
        
    search(folderName) { (folderID, error) in
            
        if let ID = folderID {
            self.upload(ID, path: filePath, MIMEType: MIMEType, onCompleted: onCompleted)
        } else {
            self.createFolder(folderName, onCompleted: { (folderID, error) in
                guard let ID = folderID else {
                    onCompleted?(nil, error)
                    return
                }
                self.upload(ID, path: filePath, MIMEType: MIMEType, onCompleted: onCompleted)
            })
        }
    }
}
    
private func upload(_ parentID: String, path: String, MIMEType: String, onCompleted: ((String?, Error?) -> ())?) {
        
    guard let data = FileManager.default.contents(atPath: path) else {
        onCompleted?(nil, GDriveError.NoDataAtPath)
        return
    }
        
    let file = GTLRDrive_File()
    file.name = path.components(separatedBy: "/").last
    file.parents = [parentID]
        
    let uploadParams = GTLRUploadParameters.init(data: data, mimeType: MIMEType)
    uploadParams.shouldUploadWithSingleRequest = true
        
    let query = GTLRDriveQuery_FilesCreate.query(withObject: file, uploadParameters: uploadParams)
    query.fields = "id"
        
    self.service.executeQuery(query, completionHandler: { (ticket, file, error) in
        onCompleted?((file as? GTLRDrive_File)?.identifier, error)
    })
}

In our public ‘uploadFile’ function, we’re trying to find an existing folder, if it doesn’t exist, we create a new one. We’re searching for the folder because we need the object identifier in order to upload the file to the folder. In the second function we’re using this object ID to set the parent of the new file. Note how one file can have many parents, meaning, they can be in multiple folders.

We created a data object with our file path, and set some parameters, including the MIME type. All that’s left is to execute the query, and that’s it.

Deleting Files

There’s only one function left to cover, and that’s the delete function. This one is just as simple as the ‘download’ function. All we need is the file id, and execute a query:

public func delete(_ fileID: String, onCompleted: ((Error?) -> ())?) {
    let query = GTLRDriveQuery_FilesDelete.query(withFileId: fileID)
    service.executeQuery(query) { (ticket, nilFile, error) in
        onCompleted?(error)
    }
}

There we go, we have our basic file operations. Let’s see how to test it out…

Testing It Out

This is just a demo app, and like all our demo apps, the UI is pretty elemental 🙂

We’ll have a Google Sign In button on the UI. Once you tap it, you will be redirected to the Google Sign In page, the app will ask for permissions to use google.com to sign in, go ahead and tap ‘Continue’:

You’ll be redirected to the Sign In page, where you can sign in with your Google Account:

After which, you will have to grant access to the app that you configured while configuring the OAuth client a couple of paragraphs ago:

Just bear in mind, if you changed the permissions scope in your code, you will have to reset your simulator to clear the keychain and ask for permissions again.

Now that we’ve signed in, we’ll upload a simple image. In this demo we’ll upload the site logo. The easiest way to do this, without having to work too much on the UI is to copy files to your app documents directory, something like this:

So our button action can be as simple as possible:

@IBAction func uploadAction(_ sender: Any) {
    if let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last {
        let testFilePath = documentsDir.appendingPathComponent("logo.png").path
        drive?.uploadFile("agostini_tech_demo", filePath: testFilePath, MIMEType: "image/png") { (fileID, error) in
            print("Upload file ID: \(fileID); Error: \(error?.localizedDescription)")
        }
    }
}

Obviously, you’ll be uploading files that make sense for you 🙂

Our masterpiece of a UI will have three buttons and a label:

Tapping the ‘Upload’ button will upload the above image to Google Drive. If we tap on the ‘List’ button next, we’ll see the content of the folder listed on the screen.

Here’s the file on the drive:

There you go, we can now use Google Drive as a storage solution. You can so some pretty interesting things with this. If the same user logs in on different devices, or on the same device but with a different app you made, they all have access to the same data.

Conclusion

In this article we covered using Google Drive for persistence. It’s pretty simple to use, and quite powerful. You could do some really interesting things with it. For example, you could easily use Google Drive to keep user’s data synced across his devices, or even across different apps that you own. I’m sure you’ll think of crazy new ways on how to use this.

It’s been fun writing this article, and I hope you learned something today. Most importantly, I hope you had some fun playing with something new 🙂 You can find all the code on the GitHub repo.

Have a nice day 🙂
~D;

More resources

13 thoughts on “Using Google Drive in Your App

  1. Jason

    Hi Friend,
    Thanks for your guide document. but i found that you missing some scope in you github project. It need one more scope: kGTLRAuthScopeDrive or it can’t list the documents under the folder.

    Reply
    1. Dejan Agostini Post author

      Hi Jason,

      Thank you for reading the blog, I appreciate it.

      You should be able to access the files/folders that you created with your app. You can only list the content of folders that your app created. This is the recommended approach by Google. Are you sure your app created the folder you’re trying to access?

      kGTLRAuthScopeDrive would definitely work, since it’s less restrictive, but you’re running a risk of accidentally deleting files on your google drive.

      Thanks again for reading and commenting and have a nice day 🙂
      ~D;

      Reply
  2. Sawsan

    Hi Dejan,

    I’m trying to list all files in the root directory of the user google drive account but I didn’t figure out how. My approach is to let the user select any file from his drive to be able to upload it on our server.

    Any idea how to implement this?
    Also, how my app can create a folder to access?

    Thanks in advance.

    Reply
  3. Dejan Agostini Post author

    Hi Sawsan,

    To list all the files in the user’s Drive change the scope from ‘kGTLRAuthScopeDriveFile’ to ‘kGTLRAuthScopeDrive’.

    You have an example in the code snippets in the article how to create a folder, check out the ‘uploadFile(_ folderName:…’ function.

    Have a nice day 🙂
    Dejan.

    Reply
    1. Sawsan

      Thank you. I got how to create a folder and it is working fine. Now I need to list only a certain type of files like PDF and images. Can I achieve this at the query level or should I filter the list after receiving it?

      Reply
  4. DM

    Thanks for the post. I’m able to successfully sign-in and grant all the required permissions. But after granting the permissions, I return to the main page with the 3 buttons. But when I click on the list button in that page, I see nothing. I have uploaded nothing; but I would like to see all the files and folders of the user logged-in. What do I need to do for that..? I have also changed the scope from ‘kGTLRAuthScopeDriveFile’ to ‘kGTLRAuthScopeDrive’. But it is not working…

    Reply
  5. DM

    Thanks for the response @Dejan Agostini. And sorry for the late response…
    Would you mind showing the code to add if we have to list all the files on Google drive…?

    Reply
  6. John Gallaugher

    Thank you for a wonderful tutorial. I’ve been able to create an app that can create a drive or access it if the drive exists, and also to save images to the drive. I need the folder and files to be accessible to others and I need to programmatically get the shareable urls for any files in the folder. Is this possible? I haven’t been able to parse out if or how from Google API docs and I’m a swift coder so the api listings in JavaScript or objc aren’t clear to me. Thank you again. This is a wonderful contribution and you are a great technical writer.

    Reply
  7. darkfox_013

    hi dejan

    nice job everything works fine as you mention in your post

    but do you know how could I create a subfolder and put a file there?

    do you have any documentation or example?

    thank you

    Reply
  8. Syed Hasnain

    Thank you for a wonderful tutorial. But i have a question that is if a user can upload a file to other user google Account. For example if the App user upload an image it should store in a unique google drive account for All user of App. is that possible so if possible please guide me. I am thankful to you

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.