TECH

Flutter: Google Drive Backup | Upload / Download Files

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

Hitesh Verma Mar 10, 2024 · 5 min. read

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
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
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.

Read next

Customized Calendar in Flutter

Hey, have you ever wanted a date-picker in your app but not in a dialog box? And you tried all the dart packages out there but cannot change their UIs

Hitesh Verma May 28, 2023 · 9 min read

Flutter: Custom Cupertino Date Picker

Hitesh Verma in TECH
May 28, 2023 · 6 min read

Flutter | Highlight searched text in results

Hitesh Verma in TECH
May 12, 2023 · 4 min read

Flutter Theming | The Right Way

Hitesh Verma in TECH
May 12, 2023 · 5 min read