본문 바로가기

[플러터, 구글드라이브] 앱 데이터를 Google Drive에 백업하기&복원하기 1탄

ironwhale 2023. 9. 16.

 Google Drive와 플러터를 연동하여 앱 데이터를 백업하고 다운로드 받을 수 있다면 저 처럼 혼자서 개발하고 있는 1인 개발자에게 비용을 최소화하기 위한 안성맞춤인 방법인거 같습니다. 그래서 사실 이부분은 파이어베이스에 데이터를 저장하거나 별도의 백엔드 서버를 이용하여 개발하고 계신분들은 크게 상관 없는 포스팅입니다. 

 

저는 이것을 이용해서 Drift를 이용한 앱의 SQLite3 db파일을 구글드라이브에 백업하고 복원하는 작업에 사용하고 있습니다. 결과는 성공졌이었고, 현재 출시된 앱에 해당 기술을 적용하여 사용하고 있습니다. 


파이어베이스 설정

일단 저는 기본적으로 애널리틱스를 사용하고 있어 파이어베이스에 앱을 연결한 상태이며 파이어베이스를 이용한 구글로그인 설정을 완료한 상태입니다. 이렇게 하면 구글 클라우드에 파이어베이스 프로젝트를 불러올수 있어 개별적으로 설정할 부분이 줄어들기 때문입니다. 


구글드라이브(Google Drive) API 사용 설정

구글 클라우드를 가셔서 연결된 파이어베이스 프로젝트를 선택하시고 API 및 서비스 가셔서 라이브러리를 클릭하십니다. 

구글클라우드
라이브러리를 클릭

그리고 라이브러리로 가셔서 구글 드라이브를 검색하시고 사용설정을 하시면 됩니다.

구글드라이브 검색
Google drive 검색

우리가 필요로 하는 구글 드라이브 API 

구글드라이브 API
구글 드라이브 API 클릭

구글 드라이브 API 사용 설정 완료


이제 구글 클라우드에서 설정할 부분은 끝났습니다. 이제 플러터 프로젝트로 가서 진행하도록 하겠습니다. 

필요 패키지

필요한 패키지가 많네요 정작 이번 포스팅에서 필요한 패키지는 위에 세개인 구글 API 부분입니다. 

  // 구글 API 
  google_sign_in: ^6.1.5
  googleapis: ^11.4.0
  googleapis_auth: ^1.4.1
  
  // 경로를 알기위해
  path_provider: ^2.1.1
  path: ^1.8.0
  
  // 리버팟
  flutter_riverpod: ^2.3.6
  riverpod_annotation: ^2.1.0

  // 파이어베이스, 구글 로그인 버튼 구현
  firebase_core: ^2.15.1
  social_login_buttons:
  
  // 리버팟 코드제너레이션 사용
  dev_dependencies:
  flutter_test:
    sdk: flutter
    
  build_runner:
  riverpod_generator: ^2.3.1
  riverpod_lint: ^2.0.3

구글 드라이브를 사용 하기 위해서는 DriveApi 인스턴스가 필요합니다. 그리고 이 인스턴스를 만들기 위해서는 구글 로그인 정보를 얻어와 Api 요청을 보낼때 헤더에 이 로그인 정보를 태워서 보내는 작업이 필요합니다. 그래서 우선 로그인 하는 부분 부터 구현해보도록 하겠습니다. 

 

아래 구글 드라이브 API 요청을 보내는 레포지토리 부분은 맨 아래 링크를 참조하였습니다. 

 

구글 로그인, 로그아웃 

저는 앱데이터를 백업할 것이지 때문에 driveAppdataScope로 설정하였습니다. 이렇게 하면 파일이 숨겨져서 혹시나 실수로 사용자가 구글드라이브에서 해당 파일을 지우는 실수를 방지할 수 있습니다. 

 

로그인 로그아웃 로직은 아래 코드를 보시면 됩니다. 

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

class BackUpRepository {

  //로그인
  Future<GoogleSignInAccount?> signIn() async {
    GoogleSignIn googleSignIn =
        GoogleSignIn(scopes: [drive.DriveApi.driveAppdataScope]);

    return await googleSignIn.signInSilently() ?? await googleSignIn.signIn();
  }

 //로그아웃
  Future<void> signOut() async {
    GoogleSignIn googleSignIn = GoogleSignIn();
    await googleSignIn.signOut();
  }
}

로그인 정보 헤더에 입력하기 

로그인 정보를 헤더에 입력하여 API 요청시 함께 보내야 정상적으로 백업, 복원 기능이 작동합니다. 

그 부분을 구현하기 위해 아래와 같은 코드를 작성하였습니다. 

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

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

  GoogleAuthClient({required this.header});

  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) {
    request.headers.addAll(header);
    return client.send(request);
  }
}

DriveAPI 만들기

다음은 제일 핵심이 되는 DriveAPI를 얻는 부분입니다. 이것이 있어야 구글 드라이브에 파일 업로드 다운로드 파일정보 얻기 등이 가능하기 때문입니다. 

 

다시 BackUpRepository가서 아래와 같은 코드를 작성합니다. 

 

class BackUpRepository {

 로그인 로그아웃 로직 생략
  
// get drive api
  Future<drive.DriveApi?> getDriveApi(
      GoogleSignInAccount googleSignInAccount) async {
    final header = await googleSignInAccount.authHeaders;
    GoogleAuthClient googleAuthClient = GoogleAuthClient(header: header);
    return drive.DriveApi(googleAuthClient);
  }
 }

 

여기까지가 구글 드라이브 사용을 위한 기본 적인 세팅이 끝났습니다.  이제부터는 업로드, 파일 정보 가져오기, 다운로드를 구현해보도록 하겠습니다. 


업로드 하기

driveFileId를 입력하면 이전에 업로드한 파일을 새로 덮어씌우는 작업을 합니다. drive.File은 구글드라이브에 업로드 할 데이터의 정보를 나타내고 File file은 로컬에 있는 앱 내부에서 구글 드라이브로 업로드할 파일을 의미합니다. 

import 'dart:io';

import 'package:google_sign_in/google_sign_in.dart';
import 'package:googleapis/drive/v3.dart' as drive;
import 'package:moor_train/backup/repository/google_auth_client.dart';
import 'package:path/path.dart' as path;

class BackUpRepository {

// 생략

//upLoad
  Future<drive.File?> upLoad(
      {required drive.DriveApi driveApi,
      required File file,
      String? driveFileId}) async {
    // 드라이브 업로드용 파일 정보
    drive.File driveFile = drive.File();

    //앱에 저장된 파일 이름 추출
    driveFile.name = path.basename(file.absolute.path);

    late final response;
    if (driveFileId != null) {
      response = await driveApi.files.update(driveFile, driveFileId,
          uploadMedia: drive.Media(file.openRead(), file.lengthSync()));
    } else {
      driveFile.parents = ["appDataFolder"];
      response = await driveApi.files.create(driveFile,
          uploadMedia: drive.Media(file.openRead(), file.lengthSync()));
    }
    return response;
  }
}

다운로드 하기

 Future<File> downLoad(
      {required String driveFileId,
      required drive.DriveApi driveApi,
      required String localPath}) async {
    drive.Media media = await driveApi.files.get(driveFileId,
        downloadOptions: drive.DownloadOptions.fullMedia) as drive.Media;

    List<int> data = [];

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

    File file = File(localPath);
    file.writeAsBytesSync(data);

    return file;
  }

 

파일 정보 가져오기 

업로드나 다운로드 시 필요한 파일아이디(fileId) 뿐 아니라 업로드 되어 있는 파일의 정보를 가져오는 부분입니다. 

$fields 부분은 Selector specifying which fields to include in a partial 이렇게 주석이 있는데 사실 어디에 활용하는지는 아직 잘 모르겠습니다. 혹시 아시는분 계신면 댓글 남겨주시면 감사하겠습니다. 

 

이 부분은 RDB 로 치면 SELECT 문에 해당하는 부분입니다. 하지만 여기서는 특정 파일명으로 드라이브에 저장된 

파일을 찾을수 있는 기능을 구현하였습니다. 

 

여기는 예외 처리를 통해 no element 예외가 발생하면 null을 리턴하도록 하였습니다. 

  Future<drive.File?> getDriveFile(
      {required drive.DriveApi driveApi, required filename}) async {
    drive.FileList fileList = await driveApi.files
        .list(spaces: "appDataFolder", $fields: "files(id,name,modifiedTime)");
    List<drive.File>? files = fileList.files;

    //Bad state: No element 발생함
    try {
      drive.File? driveFile =
          files?.firstWhere((element) => element.name == filename);
      return driveFile;
    } catch (e) {
      return null;
    }
  }

다음 포스팅 예고 

여기까지만 보셔도 플러터로 구글 드라이브를 어떻게 연결하는지 아실것입니다. 다음에는 리버팟을 이용해서 본격적으로 서비스 로직을 구성하는 방법에 대해 알아보겠습니다. 

 

제가 참조한 사이트는 아래 있으니 관심있으신 분들은 찾아보시기 바랍니다.


참고사이트

https://maylau.hashnode.dev/google-drive-app-data-backup

 

Google Drive App Data Backup

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

maylau.hashnode.dev

[인프런x코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!

https://www.inflearn.com/course/%ED%94%8C%EB%9F%AC%ED%84%B0-%EC%8B%A4%EC%A0%84

 

[인프런x코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직

이 강의를 통해 주니어급 Flutter 개발자가 중급 Flutter 개발자가 되기까지 필요한 필수 지식들을 단기간에 배워볼 수 있습니다., 중급 플러터 개발자로 업그레이드하고 싶다면? 믿고보는 코드팩

www.inflearn.com

 

댓글