In this article, we'll delve into the process of integrating Google Drive with a Flutter application to enable users to upload and download files with ease. Here we'll ensure that files are uploaded directly to the user's Google Drive account, allowing them to access and manage the files within their own Google Drive space rather than within the application's service account.
Google Drive Upload / Download files
1. Install dependencies
Install following dependencies in your pubspec.yaml file.
google_sign_in: <latest_version> googleapis: <latest_version> http: <latest_version>
Find here the complete steps to integrate the google sign-in in your project.
2. Enable Google Drive API & add it to OAuth scopes
To get access of the user's google drive for uploading & downloading files , you need to enable the Google Drive API for your firebase project.
Then go to OAuth consent screen of the project & add Google Drive API scope.
Adding Google Drive API scope
3. Create AuthService file in your flutter project
This file will contain method for initiating google sign-in with google drive permission.
import 'package:google_sign_in/google_sign_in.dart'; class AuthService { static final _instance = AuthService._(); final _googleSignIn = GoogleSignIn( scopes: ['email', 'https://www.googleapis.com/auth/drive.file'], ); AuthService._(); factory AuthService() { return _instance; } /// Sign in with Google /// returns auth-headers on success, else null Future<Map<String, String>?> googleSignIn() async { try { GoogleSignInAccount? googleAccount; if (_googleSignIn.currentUser == null) { googleAccount = await _googleSignIn.signIn(); } else { googleAccount = await _googleSignIn.signInSilently(); } return await googleAccount?.authHeaders; } catch (e) { return null; } } /// sign out google account Future<void> googleSignOut() => _googleSignIn.signOut(); }
4. Create a Drive HTTP client
To access google drive for uploading / downloading files from your app, you need to create a custom HTTP client with google auth headers received after google sign-in success .
import 'dart:convert'; import 'dart:typed_data'; import 'package:http/http.dart' as http; class DriveClient implements http.BaseClient { final _client = http.Client(); final Map<String, String> authHeaders; DriveClient(this.authHeaders); @override void close() {} @override Future<http.Response> delete( Uri url, { Map<String, String>? headers, Object? body, Encoding? encoding, }) { headers ??= {}; headers.addAll(authHeaders); return _client.delete( url, headers: headers, body: body, encoding: encoding, ); } @override Future<http.Response> get(Uri url, {Map<String, String>? headers}) { headers ??= {}; headers.addAll(authHeaders); return _client.get(url, headers: headers); } @override Future<http.Response> head(Uri url, {Map<String, String>? headers}) { headers ??= {}; headers.addAll(authHeaders); return _client.head(url, headers: headers); } @override Future<http.Response> patch( Uri url, { Map<String, String>? headers, Object? body, Encoding? encoding, }) { headers ??= {}; headers.addAll(authHeaders); return _client.patch(url, headers: headers, body: body, encoding: encoding); } @override Future<http.Response> post( Uri url, { Map<String, String>? headers, Object? body, Encoding? encoding, }) { headers ??= {}; headers.addAll(authHeaders); return _client.post(url, headers: headers, body: body, encoding: encoding); } @override Future<http.Response> put( Uri url, { Map<String, String>? headers, Object? body, Encoding? encoding, }) { headers ??= {}; headers.addAll(authHeaders); return _client.put(url, headers: headers, body: body, encoding: encoding); } @override Future<String> read(Uri url, {Map<String, String>? headers}) { headers ??= {}; headers.addAll(authHeaders); return _client.read(url, headers: headers); } @override Future<Uint8List> readBytes(Uri url, {Map<String, String>? headers}) { headers ??= {}; headers.addAll(authHeaders); return _client.readBytes(url, headers: headers); } @override Future<http.StreamedResponse> send(http.BaseRequest request) { request.headers.addAll(authHeaders); return _client.send(request); } }
5. Create DriveService file
This file will contain the methods that will actually upload & download the files to/from the google drive.
import 'dart:io'; import 'package:googleapis/drive/v3.dart' as ga; import 'package:flutter_demo/services/google_drive/auth_service.dart'; import 'package:flutter_demo/services/google_drive/drive_client.dart'; class DriveService { final _authService = AuthService(); static final _instance = DriveService._(); DriveService._(); factory DriveService() { return _instance; } /// upload file in the google drive /// returns id of the uploaded file on success, else null Future<String?> uploadFile(String fileName, String filePath) async { final file = File(filePath); // 1. sign in with Google to get auth headers final headers = await _authService.googleSignIn(); if (headers == null) return null; // 2. create auth http client & pass it to drive API final client = DriveClient(headers); final drive = ga.DriveApi(client); // 3. check if the file already exists in the google drive final fileId = await _getFileID(drive, fileName); // 4. if the file does not exists in the google drive, create a new one // else update the existing file if (fileId == null) { final res = await drive.files.create( ga.File()..name = fileName, uploadMedia: ga.Media(file.openRead(), file.lengthSync()), ); return res.id; } else { final res = await drive.files.update( ga.File()..name = fileName, fileId, uploadMedia: ga.Media(file.openRead(), file.lengthSync()), ); return res.id; } } /// download the file from the google drive /// @params [fileId] google drive id for the uploaded file /// @params [filePath] file path to copy the downloaded file /// returns download file path on success, else null Future<String?> downloadFile(String fileId, String filePath) async { // 1. sign in with Google to get auth headers final headers = await _authService.googleSignIn(); if (headers == null) return null; // 2. create auth http client & pass it to drive API final client = DriveClient(headers); final drive = ga.DriveApi(client); // 3. download file from the google drive final res = await drive.files.get( fileId, downloadOptions: ga.DownloadOptions.fullMedia, ) as ga.Media; // 4. convert downloaded file stream to bytes final bytesArray = await res.stream.toList(); List<int> bytes = []; for (var arr in bytesArray) { bytes.addAll(arr); } // 5. write file bytes to disk await File(filePath).writeAsBytes(bytes); return filePath; } /// returns file id for existing file, /// returns null if file does not exists Future<String?> _getFileID(ga.DriveApi drive, String fileName) async { final list = await drive.files.list(q: "name: '$fileName'", pageSize: 1); if (list.files?.isEmpty ?? true) return null; return list.files?.first.id; } }
6. Upload / Download file Demonstration
Here is a sample flutter widget to demonstrate how to trigger the above written codes for uploading & downloading a file.
// ignore_for_file: use_build_context_synchronously import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_demo/services/google_drive/drive_service.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; class GoogleDriveApp extends StatefulWidget { const GoogleDriveApp({super.key}); @override State<GoogleDriveApp> createState() => _GoogleDriveAppState(); } class _GoogleDriveAppState extends State<GoogleDriveApp> { String? _fileId; final _driveService = DriveService(); final _scaffoldKey = GlobalKey<ScaffoldState>(); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( key: _scaffoldKey, body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: _uploadFile, child: const Text('Upload File'), ), const SizedBox(height: 40), ElevatedButton( onPressed: _downloadFile, child: const Text('Download File'), ), ], ), ), ), ); } /// dummy method to showcase uploading of file to google drive Future<void> _uploadFile() async { _showLoader(); // 1. create a file to upload final dir = await getApplicationDocumentsDirectory(); final filePath = path.join(dir.path, 'upload.txt'); final file = File(filePath); await file.writeAsString('Hello World!!!'); // 2. upload file to drive _fileId = await _driveService.uploadFile('upload.txt', filePath); _closeLoader(); if (_fileId == null) { _showSnackbar('Failed to upload the file!'); } else { _showSnackbar('File successfully uploaded: $_fileId'); } } /// dummy method to showcase downloading of file from google drive Future<void> _downloadFile() async { if (_fileId == null) { _showSnackbar('Upload a file first!'); return; } _showLoader(); // 1. create a path to download the file final dir = await getApplicationDocumentsDirectory(); final filePath = path.join(dir.path, 'download.txt'); // 2. download the file final id = await _driveService.downloadFile(_fileId!, filePath); // 3. read downloaded file content final content = await File(filePath).readAsString(); _closeLoader(); if (id == null) { _showSnackbar('Failed to download the file!'); } else { _showSnackbar('File successfully downloaded: $content'); } } void _showSnackbar(String msg) { ScaffoldMessenger.of(_scaffoldKey.currentContext!) .showSnackBar(SnackBar(content: Text(msg))); } void _showLoader() { showDialog( context: _scaffoldKey.currentContext!, barrierDismissible: false, builder: (context) => Dialog( child: Container( width: 200, height: 200, alignment: Alignment.center, child: const CircularProgressIndicator(), ), ), ); } void _closeLoader() { Navigator.pop(_scaffoldKey.currentContext!); } }
Thank You for reading this article. Hope it helped you.