Google Drive App Data Backup
Tags: flutter, tutorial, beginners
Published: 2022-08-16 13:17:00

Backing up app data is a common action for mobile app. This article will show the steps about backing up Android and IOS files to Google Drive.

## Google SignIn

The app data read and write scope require Google Account SignIn. The detail implementation may reference to [Google SignIn without Firebase](https://dev.to/maylau/google-signin-without-firebase-8d).

## Enable Drive API

Goto `Enabled APIs & Services` on the side menu and click `+ ENABLE APIS AND SERVICES`.



![Enabled APIs & Services](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fsqzb8v6eyoc4s2zbc3y.png)

Search `google drive api`.



![Search google drive api](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vmsedgc4odzxxtykz8vy.png)



![Search google drive api](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o9tflhlpz0qm618pw8pa.png)

Click Enable `Google Drive API`.



![Enable Google Drive API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hop3uuyq7ewi1qsxe9j3.png)

## Flutter Project Config

Run `flutter pub add googleapis` to add googleapis plugin to the project.

Create a class called `GoogleDriveAppData`. The sign in logic will also put inside this class. Since we are going to use the app data storage in Google Drive, we will add the scope to `drive.DriveApi.driveAppdataScope`.

```dart
import 'package:google_sign_in/google_sign_in.dart';
import 'package:googleapis/drive/v3.dart' as drive;

class GoogleDriveAppData {
  /// sign in with google
  Future<GoogleSignInAccount?> signInGoogle() async {
    GoogleSignInAccount? googleUser;
    try {
      GoogleSignIn googleSignIn = GoogleSignIn(
        scopes: [
          drive.DriveApi.driveAppdataScope,
        ],
      );

      googleUser =
          await googleSignIn.signInSilently() ?? await googleSignIn.signIn();
    } catch (e) {
      debugPrint(e.toString());
    }
    return googleUser;
  }

  ///sign out from google
  Future<void> signOut() async {
    GoogleSignIn googleSignIn = GoogleSignIn();
    await googleSignIn.signOut();
  }
}
```

To use the drive API with its client, we will create a `GoogleAuthClient` class for passing the auth info with the drive API request.

Run `flutter pub add http` to import the http package.

```dart
import 'package:http/http.dart' as http;

class GoogleAuthClient extends http.BaseClient {
  final Map<String, String> _headers;
  final _client = http.Client();

  GoogleAuthClient(this._headers);

  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) {
    request.headers.addAll(_headers);
    return _client.send(request);
  }
}
```

Go back to the `GoogleDriveAppData` class.

Add a function to create the Drive client instance.

```dart
import 'package:google_drive_app_data/google_auth_client.dart';

class GoogleDriveAppData {
	...
	///get google drive client
  Future<drive.DriveApi?> getDriveApi(GoogleSignInAccount googleUser) async {
    drive.DriveApi? driveApi;
    try {
      Map<String, String> headers = await googleUser.authHeaders;
      GoogleAuthClient client = GoogleAuthClient(headers);
      driveApi = drive.DriveApi(client);
    } catch (e) {
      debugPrint(e.toString());
    }
    return driveApi;
  }
}
```

Google Drive has an app data folder especially for the app’s file storage. The file will save to the `appDataFolder`.

Run `flutter pub add path` to add path package for easily get file’s base name.

`drive.File` is storing the file’s information the drive having.

`io.File` is the io file which is the local file the device having.

If the file is previously uploaded to the drive, we can use the `driveFileId` to update the latest version file to the drive.

```dart
import 'dart:io' as io;

import 'package:path/path.dart' as path;

class GoogleDriveAppData {
	...
	/// upload file to google drive
  Future<drive.File?> uploadDriveFile({
    required drive.DriveApi driveApi,
    required io.File file,
    String? driveFileId,
  }) async {
    try {
      drive.File fileMetadata = drive.File();
      fileMetadata.name = path.basename(file.absolute.path);

      late drive.File response;
      if (driveFileId != null) {
        /// [driveFileId] not null means we want to update existing file
        response = await driveApi.files.update(
          fileMetadata,
          driveFileId,
          uploadMedia: drive.Media(file.openRead(), file.lengthSync()),
        );
      } else {
        /// [driveFileId] is null means we want to create new file
        fileMetadata.parents = ['appDataFolder'];
        response = await driveApi.files.create(
          fileMetadata,
          uploadMedia: drive.Media(file.openRead(), file.lengthSync()),
        );
      }
      return response;
    } catch (e) {
      debugPrint(e.toString());
      return null;
    }
  }
}
```

To get the file information of drive like id, version and modified time, we can use the following function to get the file list and compare the filename with the uploaded files.

```dart
class GoogleDriveAppData {
	...
	/// get drive file info
  Future<drive.File?> getDriveFile(
      drive.DriveApi driveApi, String filename) async {
    try {
      drive.FileList fileList = await driveApi.files.list(
          spaces: 'appDataFolder', $fields: 'files(id, name, modifiedTime)');
      List<drive.File>? files = fileList.files;
      drive.File? driveFile =
          files?.firstWhere((element) => element.name == filename);
      return driveFile;
    } catch (e) {
      debugPrint(e.toString());
      return null;
    }
  }
}
```

To download the drive file to local, we can use the following function. Basically download the data stream and write to a new file.

```dart
class GoogleDriveAppData {
	...
	/// download file from google drive
  Future<io.File?> restoreDriveFile({
    required drive.DriveApi driveApi,
    required drive.File driveFile,
    required String targetLocalPath,
  }) async {
    try {
      drive.Media media = await driveApi.files.get(driveFile.id!,
          downloadOptions: drive.DownloadOptions.fullMedia) as drive.Media;

      List<int> dataStore = [];

      await media.stream.forEach((element) {
        dataStore.addAll(element);
      });

      io.File file = io.File(targetLocalPath);
      file.writeAsBytesSync(dataStore);

      return file;
    } catch (e) {
      debugPrint(e.toString());
      return null;
    }
  }
}
```

The complete `GoogleDriveAppData` class will be like this:

```dart
import 'dart:io' as io;

import 'package:flutter/foundation.dart';
import 'package:google_drive_app_data/google_auth_client.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:googleapis/drive/v3.dart' as drive;
import 'package:path/path.dart' as path;

class GoogleDriveAppData {
  /// sign in with google
  Future<GoogleSignInAccount?> signInGoogle() async {
    GoogleSignInAccount? googleUser;
    try {
      GoogleSignIn googleSignIn = GoogleSignIn(
        scopes: [
          drive.DriveApi.driveAppdataScope,
        ],
      );

      googleUser =
          await googleSignIn.signInSilently() ?? await googleSignIn.signIn();
    } catch (e) {
      debugPrint(e.toString());
    }
    return googleUser;
  }

  ///sign out from google
  Future<void> signOut() async {
    GoogleSignIn googleSignIn = GoogleSignIn();
    await googleSignIn.signOut();
  }

  ///get google drive client
  Future<drive.DriveApi?> getDriveApi(GoogleSignInAccount googleUser) async {
    drive.DriveApi? driveApi;
    try {
      Map<String, String> headers = await googleUser.authHeaders;
      GoogleAuthClient client = GoogleAuthClient(headers);
      driveApi = drive.DriveApi(client);
    } catch (e) {
      debugPrint(e.toString());
    }
    return driveApi;
  }

  /// upload file to google drive
  Future<drive.File?> uploadDriveFile({
    required drive.DriveApi driveApi,
    required io.File file,
    String? driveFileId,
  }) async {
    try {
      drive.File fileMetadata = drive.File();
      fileMetadata.name = path.basename(file.absolute.path);

      late drive.File response;
      if (driveFileId != null) {
        /// [driveFileId] not null means we want to update existing file
        response = await driveApi.files.update(
          fileMetadata,
          driveFileId,
          uploadMedia: drive.Media(file.openRead(), file.lengthSync()),
        );
      } else {
        /// [driveFileId] is null means we want to create new file
        fileMetadata.parents = ['appDataFolder'];
        response = await driveApi.files.create(
          fileMetadata,
          uploadMedia: drive.Media(file.openRead(), file.lengthSync()),
        );
      }
      return response;
    } catch (e) {
      debugPrint(e.toString());
      return null;
    }
  }

  /// download file from google drive
  Future<io.File?> restoreDriveFile({
    required drive.DriveApi driveApi,
    required drive.File driveFile,
    required String targetLocalPath,
  }) async {
    try {
      drive.Media media = await driveApi.files.get(driveFile.id!,
          downloadOptions: drive.DownloadOptions.fullMedia) as drive.Media;

      List<int> dataStore = [];

      await media.stream.forEach((element) {
        dataStore.addAll(element);
      });

      io.File file = io.File(targetLocalPath);
      file.writeAsBytesSync(dataStore);

      return file;
    } catch (e) {
      debugPrint(e.toString());
      return null;
    }
  }

  /// get drive file info
  Future<drive.File?> getDriveFile(
      drive.DriveApi driveApi, String filename) async {
    try {
      drive.FileList fileList = await driveApi.files.list(
          spaces: 'appDataFolder', $fields: 'files(id, name, modifiedTime)');
      List<drive.File>? files = fileList.files;
      drive.File? driveFile =
          files?.firstWhere((element) => element.name == filename);
      return driveFile;
    } catch (e) {
      debugPrint(e.toString());
      return null;
    }
  }
}
```

## Result

Let’s make a simple widget with sign in and call the upload function. 

```dart
class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final GoogleDriveAppData _googleDriveAppData = GoogleDriveAppData();
  GoogleSignInAccount? _googleUser;
  drive.DriveApi? _driveApi;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            GoogleAuthButton(
              onPressed: () async {
                if (_googleUser == null) {
                  _googleUser = await _googleDriveAppData.signInGoogle();
                  if (_googleUser != null) {
                    _driveApi =
                        await _googleDriveAppData.getDriveApi(_googleUser!);
                  }
                } else {
                  await _googleDriveAppData.signOut();
                  _googleUser = null;
                  _driveApi = null;
                }
                setState(() {});
              },
            ),
            ElevatedButton(
              onPressed: _driveApi != null
                  ? () {
                      FilePicker.platform.pickFiles().then((value) {
                        if (value != null && value.files[0] != null) {
                          File selectedFile = File(value.files[0].path!);
                          _googleDriveAppData.uploadDriveFile(
                            driveApi: _driveApi!,
                            file: selectedFile,
                          );
                        }
                      });
                    }
                  : null,
              child: Text('Save sth to drive'),
            ),
          ],
        ),
      ),
    );
  }
}
```



![app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rrr0if9e4x9e6uh3tolm.png)

After the upload success, if you goto the drive web portal and click setting.



![drive portal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sxnwvv8ws1q1lryaikmx.png)

Choose `Manage Apps` from the side bar, you will find the app appear in the app list.



![Manage Apps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yti9cf140og81cpn8pa2.png)

**Notice**: if you signed in once in the previous article which means your account does not included the drive app data permission may causing 403 when upload file. You need to sign out once and sign in or other methods to let the account allow the app data permission.

**Support me if you like the content🍖**
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/N4N8D1EWN)




**Connect🍻**
[![GitHub - MayLau-CbL](https://img.shields.io/badge/GitHub-MayLau--CbL-informational?style=for-the-badge&logo=github)](https://github.com/MayLau-CbL)

[![Twitter - @MayLauDev](https://img.shields.io/badge/Twitter-%40MayLauDev-informational?style=for-the-badge&logo=twitter)](https://twitter.com/MayLauDev)

[![Hashnode - @MayLau](https://img.shields.io/badge/Hashnode-%40MayLau-informational?style=for-the-badge&logo=hashnode)](https://maylau.hashnode.dev/)