ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Flutter] Google Drive 연결
    플러터 2024. 5. 11. 15:59

    앱을 업데이트하는데 구글 드라이브 연결이 필요해서 테스트 하면서 정리한 글이다.

    플러터를 사용해 구글드라이브 API를 사용해본다(저장/불러오기)

    구글 Sign In 설정

    참고 : https://ss-sam.tistory.com/22

    구글 드라이브 설정

    라이브러리 → “google drive api” 검색 → 사용 클릭

    OAuth 동의 화면 설정

    OAuth 동의 화면 → 범위 추가 또는 삭제 → “google drive api” 검색 → 체크

     

    테스트를 위해 테스트 사용자 추가

    코드

    Package

      google_sign_in: ^6.2.1 # 필수
      googleapis: ^13.1.0 # 필수
      googleapis_auth: ^1.6.0 # 필수
      file_picker: ^8.0.3
      auth_buttons: ^3.0.2
      path_provider: ^2.1.3
      external_path: ^1.0.3
      fluttertoast: ^8.2.5 
    

    Google Sign In & Google OAuth Scope 설정

    Future<GoogleSignInAccount?> signInGoogle() async {
      GoogleSignInAccount? googleUser;
      try {
        GoogleSignIn googleSignIn = GoogleSignIn(
          scopes: [
            drive.DriveApi.driveAppdataScope,
          ],
        );
    
        googleUser = await googleSignIn.signInSilently() ?? await googleSignIn.signIn();
      } catch (e) {
        print("signInGoogle error : $e");
      }
      return googleUser;
    }
    
      
    Future<void> signOut() async {
      GoogleSignIn googleSignIn = GoogleSignIn();
      await googleSignIn.signOut();
    }
    

    GoogleAuthClient

    드라이브 API 요청할때 인증 정보를 같이 전달하는 코드

    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);
      }
    }
    

    Get Drive API

    Google Drive의 리소스에 접근할 수 있게 하는 코드

    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) {
        print("getDriveApi error : $e");
      }
      return driveApi;
    }
    

    Upload, Download, Drive File Info

    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) {
          response = await driveApi.files.update(
            fileMetadata,
            driveFileId,
            uploadMedia: drive.Media(file.openRead(), file.lengthSync()),
          );
        } else {
          fileMetadata.parents = ['appDataFolder']; // appDataFolder 수정하면 안됨.
          response = await driveApi.files.create(
            fileMetadata,
            uploadMedia: drive.Media(file.openRead(), file.lengthSync()),
          );
        }
        return response;
      } catch (e) {
        print("uploadDriveFile error : $e");
        return null;
      }
    }
    
    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) {
        print("restoreDriveFile error : $e");
        return null;
      }
    }
    
    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) {
        print("getDriveFile error : $e");
        return null;
      }
    }

     

    완성 코드

    google_auth_client.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);
      }
    }
    
    

     

    google_drive_appdata.dart

    import 'package:google_drive_test/google_auth_client.dart';
    import 'package:google_sign_in/google_sign_in.dart';
    import 'package:googleapis/drive/v3.dart' as drive;
    import 'dart:io' as io;
    import 'package:path/path.dart' as path;
    
    class GoogleDriveAppData {
      ///get google drive client
      Future<GoogleSignInAccount?> signInGoogle() async {
        GoogleSignInAccount? googleUser;
        try {
          GoogleSignIn googleSignIn = GoogleSignIn(
            scopes: [
              drive.DriveApi.driveAppdataScope,
            ],
          );
    
          googleUser = await googleSignIn.signInSilently() ?? await googleSignIn.signIn();
        } catch (e) {
          print("signInGoogle error : $e");
        }
        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) {
          print("getDriveApi error : $e");
        }
        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) {
          print("uploadDriveFile error : $e");
          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) {
          print("restoreDriveFile error : $e");
          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) {
          print("getDriveFile error : $e");
          return null;
        }
      }
    }
    
    

     

    main.dart

    설명 :

    1. 구글 로그인을 한다.
    2. Upload File 버튼을 누르면 파일 피커가 나타나고 “1.jpg”을 선택 (1.jpg를 선택 해야만 동작..)
    3. “1.jpg” 파일이 구글 드라이브에 올라가고,
    4. Download File 버튼을 누르면
    5. 다운로드 디렉토리에 “test.jpg”를 다운 받는다.
    import 'dart:io';
    
    import 'package:auth_buttons/auth_buttons.dart';
    import 'package:external_path/external_path.dart';
    import 'package:file_picker/file_picker.dart';
    import 'package:flutter/material.dart';
    import 'package:fluttertoast/fluttertoast.dart';
    import 'package:google_drive_test/google_drive_appdata.dart';
    import 'package:google_sign_in/google_sign_in.dart';
    import 'package:googleapis/drive/v3.dart' as drive;
    import 'package:path_provider/path_provider.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
      
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: MyHomePage(),
        );
      }
    }
    
    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;
      String email = "";
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(email),
                GoogleAuthButton(
                  onPressed: () async {
                    if (_googleUser == null) {
                      _googleUser = await _googleDriveAppData.signInGoogle();
                      if (_googleUser != null) {
                        _driveApi = await _googleDriveAppData.getDriveApi(_googleUser!);
                        setState(() {
                          email = _googleUser!.email;
                        });
                      }
                    } 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,
                              ).then((value){
                                Fluttertoast.showToast(
                                    msg: "upload complete",
                                    toastLength: Toast.LENGTH_SHORT,
                                    gravity: ToastGravity.BOTTOM,
                                    backgroundColor: Colors.red,
                                    textColor: Colors.white,
                                    fontSize: 16.0
                                );
                              });
                            }
                          });
                        }
                      : null,
                  child: const Text('Upload File'),
                ),
                ElevatedButton(
                  onPressed: _driveApi != null
                      ? () async {
                          String? downloadDirPath =
                              await ExternalPath.getExternalStoragePublicDirectory(
                                  ExternalPath.DIRECTORY_DOWNLOADS);
                          Directory dir = Directory(downloadDirPath);
    
                          if (!dir.existsSync()) {
                            downloadDirPath = (await getExternalStorageDirectory())!.path;
                          }
    
                          drive.File? file = await _googleDriveAppData.getDriveFile(
                              _driveApi!, "1.jpg");
                          _googleDriveAppData.restoreDriveFile(
                              driveApi: _driveApi!,
                              driveFile: file!,
                              targetLocalPath: "${dir.path}/test.jpg").then((value) {
                            Fluttertoast.showToast(
                                msg: "download complete",
                                toastLength: Toast.LENGTH_SHORT,
                                gravity: ToastGravity.BOTTOM,
                                backgroundColor: Colors.red,
                                textColor: Colors.white,
                                fontSize: 16.0
                            );
                          });
                        }
                      : null,
                  child: const Text('Download File'),
                ),
              ],
            ),
          ),
        );
      }
    }
    

     

     

    파일 리스트를 가져와서 다운받으면 좋겠지만.. 파일 리스트를 받아오는 함수는 권한 없음으로 사용 못했다.

    그리고 안드로이드는 파일 읽기/쓰기 권한 설정해야한다. IOS는 테스트 못해봄! 끝!

    Android Permission 
    
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
    
    <application
        android:requestLegacyExternalStorage="true"
        android:preserveLegacyExternalStorage="true"
        ...
    

    '플러터' 카테고리의 다른 글

    [Flutter] DB indexing 사용 예시  (0) 2024.05.17
    [Dart] Mixin, with  (0) 2024.05.16
    [Flutter] BuildContext  (0) 2024.04.15
    [Flutter] Custom Button 만들기  (0) 2024.04.07
    [Flutter] Github Action + Firebase distribution 안드로이드 배포  (1) 2024.04.06
Designed by Tistory.