-
[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
설명 :
- 구글 로그인을 한다.
- Upload File 버튼을 누르면 파일 피커가 나타나고 “1.jpg”을 선택 (1.jpg를 선택 해야만 동작..)
- “1.jpg” 파일이 구글 드라이브에 올라가고,
- Download File 버튼을 누르면
- 다운로드 디렉토리에 “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