FlutterでGoogle Driveを使ったバックアップ

Flutterに限りませんが、Google Driveを使用してアプリケーション領域にバックアップ、リストアをしたい場合、色々と設定がややこしくて詰まるので、自分用の備忘録として確認すべき項目をリスト化しておきます。

他の人にも役立てば幸いです。


自分のPC

  • keytoolが使用できる事(自分の場合、パスが通っていないので[Java > jdk-xxx > bin](keytool.exeがあるフォルダ)まで移動)
  • コマンドでデバッグキーストアの取得
keytool -list -v -alias androiddebugkey -keystore C:\Users\YOUR_USERNAME\.android\debug.keystore

* [YOUR_USERNAME] 自分のユーザー名
* [キーストアのパスワード] 通常は「android」
  • 表示された「SHA-1」(key1)と「SHA-256」(key2)をコピペしておく
  • コマンドでリリースキーストアの作成と取得
keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000

* [my-release-key.keystore] 生成されるキーストアの名前
* [my-key-alias] キーエイリアスの名前
  • キーストアのパスワードとキーエイリアスのパスワードを設定後、名前、組織部署、組織名(会社名)、都市名(市)、州(県)、国コード(JP)を入力して、[はい]でキーストアの作成
  • コマンドで作成したキーストアのフィンガープリントを取得
keytool -list -v -alias my-key-alias -keystore my-release-key.keystore

* [my-key-alias] 先程の作成したキーエイリアスの名前
* [my-release-key.keystore] 先程の作成したキーストアの名前
  • キーストアのパスワードとキーエイリアスのパスワードを入力して、表示された「SHA-1」(key3)と「SHA-256」(key4)をコピペしておく

Google Play

  • アプリの登録(大前提)(テスト公開でOK)
  • [設定 > アプリの署名]から「SHA-1 証明書のフィンガープリント」(key5)「SHA-256 証明書のフィンガープリント」(key6)を取得

Firebase

  • プロジェクトの作成(大前提)
  • アプリの追加(Android)
  • [Authentication > Sign-in method(ログイン方法)]のログインプロバイダで「Google」を有効にする
  • [プロジェクトの概要 > プロジェクトの設定]もしくは[プロジェクト > アプリ]を選択
  • [マイアプリ]のアプリを選択して「フィンガープリントを追加」する(key1, key2, key3, key4, key5, key6)
  • google-services.jsonをダウンロードしておく

Google Cloud

  • プロジェクトの作成(大前提)
  • [ナビゲーションメニュー > APIとサービス > ライブラリ]に移動
  • 「Google Drive」を検索して、「Google Drive API」を有効にする
  • [APIとサービス > OAuth 同意画面]に移動
  • [公開ステータス]は「テスト」(公開したら本番環境へ)
  • [ユーザーの種類]は「外部」
  • [スコープの設定]は「Google Drive」で検索して「…/auth/drive.file」と「…/auth/drive.appdata」を追加

Flutter

  • (ここからルートはアプリのトップディレクトリ)
  • [android/app]にgoogle-services.jsonを追加
  • [android/build.gradle]に以下を追加
    dependencies {
        ...

        // versionはその都度合わせる
        classpath 'com.google.gms:google-services:4.3.15'
    }
  • [android/app/build.gradle]に以下を追加
plugins {
    ...
    id 'com.google.gms.google-services'
}
dependencies {
    ...
    implementation(platform("com.google.firebase:firebase-bom:32.7.0"))
    implementation("com.google.firebase:firebase-analytics")
    implementation("com.google.firebase:firebase-auth")
    implementation("com.google.firebase:firebase-firestore")
}
  • 「flutter pub add」コマンドで必要なパッケージを取得
  •   google_sign_in
  •   firebase_core
  •   firebase_auth
  •   flutter_secure_storage
  •   googleapis
  •   googleapis_auth
  •   http

Flutterでの接続について

設定はGoogle Play, Firebase, Google Cloudがちゃんとしていれば大丈夫のはずです。下はバックアップ、リストア、削除のクラス(google_drive_api.dart)です。

*参考サイト「【Flutter】GoogleDriveへのバックアップ・リストア機能を実装するまでの道のり

import 'dart:async';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:googleapis/drive/v3.dart' as ga;
import 'package:http/io_client.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path;
import 'dart:io' as io;

class GoogleApi {
  Future<bool> uploadFileToGoogleDrive() async {
    bool isUpload = false;
    final completer = Completer<bool>();

    try {
      GoogleSignIn googleSignIn = GoogleSignIn(
        scopes: [
          'https://www.googleapis.com/auth/drive.file',
          'https://www.googleapis.com/auth/drive.appdata',
        ],
      );
      GoogleSignInAccount? googleSignInAccount = await googleSignIn.signIn();

      if (googleSignInAccount != null) {
        var client = GoogleHttpClient(await googleSignInAccount.authHeaders);
        var drive = ga.DriveApi(client);
        ga.FileList list = await drive.files.list(spaces: 'appDataFolder', $fields: 'files(id, name, modifiedTime)');

        // Google Driveに残っているアプリフォルダ内のデータを削除しておく
        if (list.files != null && list.files!.isNotEmpty) {
          for (var i = 0; i < list.files!.length; i++) {
            String? gdId = list.files![i].id;
            if (gdId != null) {
              await drive.files.delete(gdId);
            }
          }
        }

        ga.File fileToUpload = ga.File();

        String dbPath = "ファイルのフルパス";
        if (io.File(dbPath).existsSync()) {
          io.File dbFile = io.File(dbPath);

          fileToUpload.parents = ["appDataFolder"];
          fileToUpload.modifiedTime = DateTime.now();
          fileToUpload.name = path.basename(dbFile.path);
          await drive.files.create(
            fileToUpload,
            uploadMedia: ga.Media(dbFile.openRead(), await dbFile.length()),
          );
          isUpload = true;
          completer.complete(isUpload);
        } else {
          // バックアップするファイルが見つかりません
          completer.complete(isUpload);
        }
      } else {
        // Googleのサインインに失敗しました
        completer.complete(isUpload);
      }
    } catch (e) {
      // エラー
      completer.completeError(e);
    }

    return completer.future;
  }

  Future<bool> downloadGoogleDriveFile() async {
    bool isDownload = false;
    final completer = Completer<bool>();

    try {
      GoogleSignIn googleSignIn = GoogleSignIn(
        scopes: [
          'https://www.googleapis.com/auth/drive.file',
          'https://www.googleapis.com/auth/drive.appdata',
        ],
      );
      GoogleSignInAccount? googleSignInAccount = await googleSignIn.signIn();

      if (googleSignInAccount != null) {
        var client = GoogleHttpClient(await googleSignInAccount.authHeaders);
        var drive = ga.DriveApi(client);

        // Google Driveのアプリフォルダに保存されている一つ目のデータを取得
        ga.FileList list = await drive.files.list(spaces: 'appDataFolder', $fields: 'files(id, name, modifiedTime)');
        String? gdID = (list.files != null && list.files!.isNotEmpty) ? list.files!.first.id : "";

        if (gdID != null && gdID.isNotEmpty) {
          ga.Media file = await drive.files.get(gdID, downloadOptions: ga.DownloadOptions.fullMedia) as ga.Media;

          String dbPath = "ファイルのフルパス";
          io.File dbFile = io.File(dbPath);

          List<int> dataStore = [];
          file.stream.listen((data) {
            dataStore.insertAll(dataStore.length, data);
          }, onDone: () {
            dbFile.writeAsBytes(dataStore);
            isDownload = true;
            completer.complete(isDownload);
          }, onError: (error) {
            // エラー
            completer.completeError(error);
          });

        } else {
          // ダウンロードするデータが見つかりませんでした
          completer.complete(isDownload);
        }
      } else {
        // Googleのサインインに失敗しました
        completer.complete(isDownload);
      }
    } catch (e) {
      // エラー
      completer.completeError(e);
    }

    return completer.future;
  }

  // バックアップデータの削除
  Future<bool> deletedGoogleDriveFile() async {
    bool isDelete = false;
    final completer = Completer<bool>();

    try {
      GoogleSignIn googleSignIn = GoogleSignIn(
        scopes: [
          'https://www.googleapis.com/auth/drive.file',
          'https://www.googleapis.com/auth/drive.appdata',
        ],
      );
      GoogleSignInAccount? googleSignInAccount = await googleSignIn.signIn();

      if (googleSignInAccount != null) {
        var client = GoogleHttpClient(await googleSignInAccount.authHeaders);
        var drive = ga.DriveApi(client);
        ga.FileList list = await drive.files.list(spaces: 'appDataFolder', $fields: 'files(id, name, modifiedTime)');

        // Google Driveに残っているアプリフォルダ内のデータを削除しておく
        if (list.files != null && list.files!.isNotEmpty) {
          for (var i = 0; i < list.files!.length; i++) {
            String? gdId = list.files![i].id;
            if (gdId != null) {
              await drive.files.delete(gdId);
            }
          }
        }
        isDelete = true;
        completer.complete(isDelete);
      } else {
        // Googleのサインインに失敗しました
        completer.complete(isDelete);
      }
    } catch (e) {
      // エラー
      completer.completeError(e);
    }

    return completer.future;
  }
}

class GoogleHttpClient extends IOClient {
  final Map<String, String> _headers;
  GoogleHttpClient(this._headers) : super();
  @override
  Future<IOStreamedResponse> send(http.BaseRequest request) => super.send(request..headers.addAll(_headers));
  @override
  Future<http.Response> head(Uri url, {Map<String, String>? headers}) {
    headers = headers ?? {};
    headers.addAll(_headers);
    return super.head(url, headers: headers);
  }
}

使う場合は下みたいな感じで。

import 'package:notebook/google_drive_api.dart';

// こんな感じで使うか
bool isUpload= await GoogleApi().uploadFileToGoogleDrive();
message = (isUpload) ? "バックアップ成功" : "バックアップ失敗";

// こんな感じで使う
GoogleApi().uploadFileToGoogleDrive().then((bool isUpload) {
  var message = (isUpload) ? "バックアップ成功" : "バックアップ失敗";
});

これで上手くいきました。
ちなみにここまでBingと二人三脚でやりました。Bing、めちゃくちゃ優秀です。


ここまでやって接続できない、エラーが出る人は私にとっても勉強になるので教えてください。


レシピブック(レシピ登録アプリ)
手書きノート(メモ登録アプリ)

投稿日

カテゴリー:

ブログランキング・にほんブログ村へ

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です