Firebase Storage 사용해 보기

Firebase Cloud Storage Documentation

firebase_core | Flutter Package
firebase_storage | Flutter Package
http | Dart Package
image_picker | Flutter Package
permission_handler | Flutter Package

Firebase 세팅하기 - Flutter 3.0 이후
Firebase 세팅하기 - Flutter 3.0 이전
Firebase Firestore 사용해 보기 1편
Firebase Firestore 사용해 보기 2편
Firebase Realtime 사용해 보기

Lorem Picsum

이번에는 Firebase의 이미지, 동영상과 같은 컨텐츠를 저장할 수 있는 저장소인 Storage에 대해서 알아보도록 하겠다.

이전 시간까지 Firestore, Realtime Database에 대해서 살펴보았는데, 해당 블로그를 보신 분들은 궁금하셨던 점이 있었을 겁니다. 바로 이미지나 동영상은 어떻게 저장을 할까 ? Firestore, Realtime에는 이런 기능을 제공하고 있지 않다는 것을 눈치챘을 겁니다.
이미지, 동영상과 같은 컨텐츠 저장을 목적으로 애플리케이션 개발에 도움을 주는 기능을 갖추고 있는 저장소가 Firebase Storage입니다.

SNS 또는 커머스 서비스들에서 사용되는 이미지를 앱에서 보여주려면 네트워크 이미지를 사용하여 이미지를 노출시키고 있는데, 이러한 이미지는 어딘가에 저장되어 있다가 URL 주소를 통해서 이미지를 다운받는 과정을 거친 후 앱에서 노출시킬 수 있습니다.

Firestore, Realtime에는 이러한 기능은 제공하지 않고 Storage를 통해서 제공하고 있습니다.

Storage 사용 방법에 대해서 살펴보고, Storage와 Firestore를 통해서 실제 앱을 개발하실 때에 어떤 방식으로 개발해야 하는지에 대해서도 함께 살펴보도록 하겠습니다.

Firebase

Firebase의 Storage 탭을 클릭하여 Storage를 시작하자.

Storage에서도 Firestore, Realtime과 동일하게 우선 보안 규칙을 선택해야 하는데, 테스트 모드로 시작하도록 하겠다. 배포시 프로덕션으로 변경해야 하니 실제 배포되는 앱이라면 프로덕션을 선택하시면 된다.
물론 언제든지 보안규칙은 수정이 가능하다.

실제 저장소 Resion을 선택해야 하는데, Firestore를 이미 사용하고 있다면 Firestore Resion을 따라가게 된다. 완료 버튼을 눌러 Cloud Storage 버킷 생성을 마무리 하자.

클릭 몇 번으로 Storage가 생성이 되었다.

Rules 탭으로 이동해보면 보안 규칙을 수정하고 모니터링을 할 수 있다.
테스트 모드인 경우 아래와 같이 날짜가 생성일로 한 달동안 사용 가능하도록 되어있는데, 원하는 날짜로 변경 가능하니 맘놓고 테스트 해보셔도 된다.

사용량 탭에서는 Firebase의 다른 기능과 동일하게 사용량 체크가 가능하다.

다시 처음의 Files 탭으로 이동하여 파일업로드 옆에 있는 폴더 모양을 클릭하여 새로운 폴더를 하나 생성해 주자. 생성된 폴더를 클릭하면 상단 저장소 path가 변경되는 것을 확인할 수 있다.

아무런 이미지를 사용해서 파일 업로드를 클릭한다음 업로드를 진행해보자. 업로드가 완료되면 저장소에 새로운 이미지 파일하나가 저장된 것을 확인할 수 있다.

저장소 위치와 access token 등의 정보를 확인할 수 있으며, Flutter에서 저장소 위치를 통해 다운로드 URL을 가져올 수도 있고, 토큰을 통해서도 가능하다.

이름이 하이퍼링크로 되어있는 것을 확인할 수 있는데, 해당 링크를 클릭하면 URL주소를 확인할 수 있다.

생성한 이미지를 클릭하여 삭제를 해보자. 삭제한 이 후에 test 폴더로 생성된 폴더도 삭제해 준다.

여기까지 Firebase Storage 대시보드를 사용하는 방법에 대해서 살펴보았다. 이제 Flutter에서 컨텐츠(이미지, 동영상 등)를 업로드하고 업로드 된 이미지를 가져오는 방법에 대해서 알아보자.

Flutter

Flutter에서 컨텐츠를 업로드하고 업로드 된 파일에 접근하는 다양한 방식에 대해서 살펴보고, 업로드 된 파일을 다운로드 URL, 디바이스 메모리, 로컬 저장소 등으로 가져오는 방법까지 자세히 알아보고 실제 앱 개발시 Storage파일과 Firestore를 어떻게 활용해서 처리해야 하는지에 대해서도 살펴보도록 하겠다.

dependencies

dependencies:
	cloud_firestore: ^4.4.3
	firebase_storage: ^11.0.15

Upload

먼저 Storage에 컨텐츠를 업로드 하는 방법에 대해서 살펴보도록 하자.

아래와 같이 코드를 작성한 다음 실행시켜 보자.

FirebaseStorage _storage = FirebaseStorage.instance;
Reference _ref = _storage.ref("test/text");
_ref.putString("Hello World !!");

Storage 대시보드로 이동해서 데이터가 저장된 것을 확인해보면, 우리가 ref안에 넣은 경로(path)로 텍스트 파일이 저장된 것을 확인할 수 있다.

text를 클릭하여 데이터를 확인하자. 정확히 "Hello World !!"라는 텍스트 파일이 저장되었다.

위에서 작성한 reference는 아래와 같이 작성하여도 기능은 동일하다. 편한 방법으로 사용하면 된다.

FirebaseStorage _storage = FirebaseStorage.instance;
Reference _ref = _storage.ref().child("test").child("text");
_ref.putString("Hello World !!");

만약에 같은 경로의 파일에 텍스트를 변경하고 저장하면 어떻게 될까 ? 파일이 하나 더 생성이 될까 ? 아니면 텍스트 파일이 변경이 될까 ? 그게 아니라면 에러가 발생할까 ?

테스트를 해보자. 이번에는 경로는 동일하게 두고 문자열만 변경하여 다시 저장해보자.

결과는 텍스트 파일이 변경된 것을 확인할 수 있다. 앞으로도 경로에 저장된 컨텐츠를 변경할 것이 아니라면 경로 설정에 주의하여야 할 것이다.

FirebaseStorage _storage = FirebaseStorage.instance;
Reference _ref = _storage.ref("test/text");
_ref.putString("Hello Flutter !!");

이번에는 이미지를 저장하도록 해보자. 이미지를 저장하는 방법은 에셋에 미리 저장된 이미지를 업로드 하는 방법, 갤러리에서 선택된 이미지를 업로드 하는 방법 그리고 네트워크 이미지를 업로드하는 경우가 있다.

Storage SDK에서 제공하는 업로드 방식으로는 File, Uint8List, RawData 모두 가능하다.

천천히 살펴보도록 하자.

Assets

에셋에 미리 저장한 이미지를 Storage에 업로드 하는 방법에 대해서 살펴보도록 하겠다.

우선 project 수준에서 assets 폴더를 생성하고, 테스트 할 이미지 파일을 넣어주자.

다음 pubspec.yaml 파일로 와서 assets 부분에 생성한 assets/ 폴더까지만 경로를 등록하고, flutter clean -> flutter pub get을 해주고 앱을 재 빌드하자.

우선 image라는 에셋 경로를 문자열로 선언하고, imageName이라는 이미지 이름을 선언하였다.

putFile 기능을 사용하여 File 객체를 넣어주면 되는데, File 객체를 생성하는 순서는 아래 코드를 참고하면 된다.

File 객체 생성시 생성 방법은 아래 공유한 방법 외에도 다른 방법들도 있다.

String _image = "assets/images/marvel/marvel_02.jpeg";
String _imageName = "marvel_02";
Directory systemTempDir = Directory.systemTemp;
ByteData byteData = await rootBundle.load(_image);
File file = File("${systemTempDir.path}/$_imageName.jpeg");
await file.writeAsBytes(byteData.buffer.asUint8List(
                    byteData.offsetInBytes, byteData.lengthInBytes));

FirebaseStorage.instance.ref("test/$_imageName").putFile(file);

해당 기능을 실행하고 Storage 대시보드에서 파일이 저장이 되었는지 확인해보자. 정상적으로 이미지 파일이 저장된 것을 확인할 수 있다. 하이퍼링크로 된 이름을 클릭하면 네트워크에 이미지가 업로드 되어있는 것을 확인할 수 있다.

Network

이번엔 Network 이미지를 가져와 Storage에 업로드 하는 방법에 대해서 살펴보도록 하겠다. 네트워크 이미지를 가져오기 위해서는 REST API GET 방식을 통해 가져와야 된다. 가져온 Image는 File 또는 Uint8List 형태로 변환 후 Storage에 업로드할 수 있다.

네트워크 이미지 사용은 "Lorem Picusm"의 이미지 관련 무료 API를 사용하여 구현하도록 하겠습니다.

dependencies
dependencies:
	http: ^0.13.5
File

Network 이미지를 http 통신을 사용하여 Uint8List 형태로 리턴 받아 시스템 파일 시스템 경로로 File 객체를 생성하여 putFile을 사용해서 Storage에 저장하면 된다.

Asset을 저장할 때 사용한 File형태가 동일한 것이다.

Future<File?> _fetchNetworkToFile() async {
    try {
      http.Response _response = await http
          .get(Uri.parse("https://picsum.photos/seed/picsum/200/300"));
      if (_response.statusCode == 200) {
        Uint8List _uint8List = _response.bodyBytes;
        ByteData _byte = ByteData.view(_uint8List.buffer);
        Directory _systemDirectory = Directory.systemTemp;
        File _file = await File('${_systemDirectory.path}/picsum').writeAsBytes(
            _uint8List.buffer
                .asUint8List(_byte.offsetInBytes, _byte.lengthInBytes));
        return _file;
      } else {
        return null;
      }
    } on HttpException catch (error) {
      logger.e(error);
      return null;
    }
  }
File? _file = await _fetchNetworkToFile();
if (_file != null) {
	FirebaseStorage.instance.ref("test/picsum").putFile(_file);
}

Firebase Storage에서도 확인해 보면 정상적으로 저장이 되었다.

Uint8List

이번엔 Unit8List로 Storage에 저장하는 방법에 대해서 살펴보자. API 호출로 리턴받은 response의 bodyBytes를 그냥 저장하면 된다. File 형태로 저장하는 것보다 훨씬 간편하다.

Future<Uint8List?> _fetchNetworkToUint8List() async {
    try {
      http.Response _response =
          await http.get(Uri.parse("https://picsum.photos/200/300/?blur"));
      if (_response.statusCode == 200) {
        Uint8List _unit8List = _response.bodyBytes;
        return _unit8List;
      } else {
        return null;
      }
    } on HttpException catch (error) {
      logger.e(error);
      return null;
    }
  }
Uint8List? _image = await _fetchNetworkToUint8List();
if (_image != null) {
		FirebaseStorage.instance.ref("test/blur").putData(_image);
}

이번에는 Http 통신을 사용하지 않고 NetworkImage를 사용해서 저장하는 방법에 대해서도 살펴보겠다. Http 통신을 사용하는 것보다 쉽게 처리할 수 있다.

Uint8List _image = (await NetworkAssetBundle(
                            Uri.parse("https://picsum.photos/200/300/?blur"))
                        .load("https://picsum.photos/200/300/?blur"))
                    .buffer
                    .asUint8List();
FirebaseStorage.instance.ref("test/blur2").putData(_image);

ImagePicker

Storage에 이미지를 업로드할 수 있는 방법인 Image 선택기를 사용한 업로드에 대해서 알아보도록 하겠다.

아마도 이미지 업로드 방식 중 가장 자주 사용하게 될 방법일 것이다. Flutter 라이브러리 중 image_picker를 사용해서 갤러리의 이미지를 선택 받아 Image파일을 File 또는 Uint8List로 변환 후 Storage에 저장하면 된다.

위에서 File, Uint8List를 변환하는 과정을 알아봤으니, 어렵지는 않을 것이다.

dependencies
dependencies:
	image_picker: ^0.8.7
	permission_handler: ^10.2.0

ImagePicker의 사용 방법은 매우 간단하다. 아래 코드를 실행해보자. 에러가 발생했을텐데, 해당 에러가 발생하는 이유는 Permission과 관련이 있다.

에러가 발생하지 않은 경우는 이미 사진에 대한 권한을 받은 상태여서 에러가 발생하지 않았을 것이다.

Permission을 ImagePicker를 사용하기 전에 먼저 권한을 요청 받도록 하여야 한다.

ImagePicker _picker = ImagePicker();
await _picker.pickImage(source: ImageSource.gallery);

안드로이드에서는 Permission을 요청하지 않아도 사용할 수 있고, IOS에서는 반드시 사진, 카메라에 대한 권한을 받아야 한다.

Android

먼저 안드로이드에서 설정을 하나 해줘야 하는데 아래와 같이 해주면 된다. 해당 권한을 추가하였는데도, 에러가 발생한다면 permission_handler에서 API 버전 타켓과 관련한 설정을 추가적으로 해주면 된다.

project > android > app > src > main > AndroidManifest.xml

<application
        ...     
        android:requestLegacyExternalStorage="true" 
        ...
		...

IOS에서는 권한을 받지 않은 경우 아래와 같은 에러가 발생한다.

[access] This app has crashed because it attempted to access privacy-sensitive data without a usage description.  The app's Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data.

IOS

IOS Permission 사용에 필요한 설정을 진행해보자.

아래 경로로 이동하여 Info 파일에 아래 key를 추가하자. 밑에 문자열은 권한 팝업에 나와있는 문구를 넣어주는 값으로 원하는 문구를 넣어서 사용하면 된다.

ios > Runner > Info.plist

<key>NSPhotoLibraryUsageDescription</key>
<string>This app requires read and write access to the photo library</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app requires read and write access to the photo library</string>

이번에는 Podfile에도 권한 사용을 등록해야 하는데, IOS 14.0 타겟을 사용하는 경우 추가하여야 한다.

ios > Podfile

target.build_configurations.each do |config|
    config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
        '$(inherited)',

        ## dart: PermissionGroup.calendar
        # 'PERMISSION_EVENTS=1',

        ## dart: PermissionGroup.reminders
        # 'PERMISSION_REMINDERS=1',

        ## dart: PermissionGroup.contacts
        # 'PERMISSION_CONTACTS=1',

        ## dart: PermissionGroup.camera
        # 'PERMISSION_CAMERA=1',

        ## dart: PermissionGroup.microphone
        # 'PERMISSION_MICROPHONE=1',

        ## dart: PermissionGroup.speech
        # 'PERMISSION_SPEECH_RECOGNIZER=1',

        ## dart: PermissionGroup.photos
        # 'PERMISSION_PHOTOS=1',

        ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
        # 'PERMISSION_LOCATION=1',

        ## dart: PermissionGroup.notification
        # 'PERMISSION_NOTIFICATIONS=1',

        ## dart: PermissionGroup.mediaLibrary
        # 'PERMISSION_MEDIA_LIBRARY=1',

        ## dart: PermissionGroup.sensors
        # 'PERMISSION_SENSORS=1',   

        ## dart: PermissionGroup.bluetooth
        # 'PERMISSION_BLUETOOTH=1',

        ## dart: PermissionGroup.appTrackingTransparency
        # 'PERMISSION_APP_TRACKING_TRANSPARENCY=1',

        ## dart: PermissionGroup.criticalAlerts
        # 'PERMISSION_CRITICAL_ALERTS=1'
      ]
    end

퍼미션이 필요한 기능에 주석을 해제하고 사용하면 된다.

## dart: PermissionGroup.photos
'PERMISSION_PHOTOS=1',

IOS인 경우 permission_handler 사용해서 사진 권한을 요청한 뒤, ImgaePicker를 사용하여 갤러리에 있는 이미지를 가져온다.
선택된 이미지의 XFile을 File 객체로 변환 후 Storage에 저장하면 된다.

if (Platform.isIOS) {
	await Permission.photosAddOnly.request();
}
ImagePicker _picker = ImagePicker();
XFile? _pick = await _picker.pickImage(source: ImageSource.gallery);
if (_pick != null) {
	File _file = File(_pick.path);
FirebaseStorage.instance
			.ref("test/picker/test_image")
			.putFile(_file);
}

선택된 이미지가 정상적으로 저장이 되었다.

추가로 Storage에 저장시 메타데이터를 추가하여 저장도 가능하다.

FirebaseStorage.instance.ref("test/picker/test_image_2").putFile(
                        _file,
                        SettableMetadata(
                          customMetadata: {
                            "meta": "2",
                          },
                        ),
                      );

이번에는 File 타입이 아닌 Uint8List 이미지를 저장해보자. Uint8List 이미지를 저장하기 위해 SettableMetadata의 contentType을 사용하여야 한다.
contentType을 image/jpeg로 반드시 넣어주어야 이미지를 제대로 업로드 할 수 있다.

ImagePicker _picker = ImagePicker();
XFile? _pick = await _picker.pickImage(source: ImageSource.gallery);
if (_pick != null) {
	Uint8List _bytes = await _pick.readAsBytes();
	FirebaseStorage.instance.ref("test/picker/test_picker").putData(
                        _bytes,
                        SettableMetadata(
                          contentType: "image/jpeg",
                        ),
                      );
                }

Firebase Storage에도 정상적으로 업로드가 되었다.

Multi Image Picker

이번에는 여러 이미지를 선택해서 한 번에 저장시켜 보자. 한 개의 이미지를 Storage에 저장하는 것과 동일 하다고 보면 된다.

ImagePicker _picker = ImagePicker();
List<XFile> _images = await _picker.pickMultiImage();
if (_images.isNotEmpty) {
	for (int i = 0; i < _images.length; i++) {
		File _file = File(_images[i].path);
		await FirebaseStorage.instance
                        .ref("test/multi/image_$i")
                        .putFile(_file);
                  }
                }

정상적으로 Storage에 이미지가 저장이 되었다.

Download

이제 이미지를 Storage에 저장하는 방법에 대해서 살펴봤으니, 다운로드 받는 방법에 대해서 알아보자.
Storage에 이미지를 다운로드 받는 방법은 생각보다 어렵지 않고 간단하다.
Upload와 동일하게 이미지를 다운로드 할때에도 메모리, 파일, 네트워크 모두 가능하다. 방식에 따라 살펴보자.

URL

아마도 Storage에 이미지를 가져오는 방법 중 가장 쉬운 방법인 Network 이미지로 다운로드를 받는 방법이다. url을 주소창에 붙여넣기 하면 Upload된 이미지가 정상적으로 노출이 된다.

Reference _ref = FirebaseStorage.instance.ref().child('test/multi/image_0');
String _url = await _ref.getDownloadURL();
logger.e(_url);

Memory

이번에는 메모리에 다운로드를 받는 방법이다. 메모리에 다운로드를 받을 경우 주의할 점이 하나 있는데, 앱의 가용 메모리 보다 큰 용량의 이미지를 다운로드 받을 경우 앱이 죽어버린다는 것이다.

Uint8List로 가져와서 Image.memory를 활용하면 된다.

Reference _ref = FirebaseStorage.instance.ref().child('test/multi/image_1');
final Uint8List? _data = await _ref.getData(1024 * 1024);
Image _image = Image.memory(_data!);

listAll

이번엔 같은 경로에 있는 이미지를 한 번에 전체를 불러오는 방법에 대해서 살펴보겠다. 이 방법은 한 번에 reference 전체를 읽어오다 보니 딜레이가 다소 걸리니 유의하여야 한다.

 ListResult _result = await FirebaseStorage.instance.ref("test/multi").listAll();
for (final e in _result.items) {
	logger.e(await e.getDownloadURL());
}

list

이번에는 원하는 컨텐츠 만큼만 호출하는 기능이다. maxResults 값으로 컨텐츠 갯수를 제한할 수 있고, pageToken을 사용하여 페이지네이션 처리를 할 수도 있지만, Storage에 직접 접근해서 사용하지 않다보니 다루지 않겠다.

 ListResult _result = await FirebaseStorage.instance
                    .ref("test/multi")
                    .list(const ListOptions(maxResults: 2));
for (final e in _result.items) {
	logger.e(await e.getDownloadURL());
}

metadata

Storage 컨텐츠의 메타데이터도 가져올 수 있다.

FullMetadata metadata = await FirebaseStorage.instance
                    .ref("test/multi")
                    .getMetadata();

Delete

이번에는 저장소에 Upload된 컨텐츠를 삭제하는 방법에 대해서 살펴보자. 경로만 넣어주고 delete를 호출해주면 삭제가 된다.

await FirebaseStorage.instance
                    .ref("test/multi/image_0")
                    .delete();

Storage 저장소에서도 삭제된 것을 확인할 수 있다.

실시간으로 사용량이 변화되는 것을 확인하여 리소스 관리를 꼭 하셔야 한다.


지금까지 Storage 사용 방법에 대해서 살펴보았다. 잘 사용할 수 있을 것 같은가.. 라는 의문이 들었을 것이다.

Storage는 저장소이지 Firestore, realtime과 같은 데이터베이스가 아니다.

실제로 Storage에 직접 접근해서 데이터를 불러오고 하는 일은 없다. 데이터베이스에 다운로드 Url을 넣어놓고 사용하는 경우가 거의 대부분이다.
예를 들어 로그인한 사용자의 프로필을 가져와야 하는데, 그때마다 데이터베이스, Storage에 접근해서 이미지를 다운받아 와야 한다면 지연이 발생하기도 하고, 리소스에도 이슈가 발생할 것이다.
데이터베이스에 Storage에 저장된 컨텐츠의 Url을 바로 저장하여 Storage 접근 없이 데이터베이스에서만 쿼리를 통해 프로필 정보를 세팅해 주면 될것이다.

간단한 예제를 통해서 실제 앱 개발시 Storage와 데이터베이스를 어떻게 사용하는지 살펴보자. 저장소는 Firestore를 사용할 것이고, Firestore 설정이 되어있지 않다면 위에 공유한 글을 보고 먼저 세팅 부터 해야 한다.

SNS Feed

예제 기능은 SNS처럼 이미지를 보여주는 Feed를 생성하고, 생성된 Feed를 읽어오고 삭제하는 것까지 다뤄보도록 하겠다.
설명 중 UI 관련된 부분은 다루지 않고 전체 소스 코드로 공유할 것이고, 편하게 전체 코드를 가져가서 사용하게 하기 위해서 stateful 위젯으로만 개발하도록 하겠다.

전체 소스 코드는 Git 저장소에 공유하도록 하겠다.

dependencies

dependencies:
	cloud_firestore: ^4.4.3
	firebase_core: ^2.7.0
	firebase_storage: ^11.0.15
	image_picker: ^0.8.7
	permission_handler: ^10.2.0

UI 코드는 아래 공유된 전체 코드를 참고하길 바란다. 우선 UI 구조는 3x3 배열로 GridView를 사용하여 Firestore에 저장된 네트워크 이미지 Url 주소를 가져와 노출시켜 주고, FloatingActionButton에 리프레시 버튼과 사진 업로드 버튼을 배치하였다.

Object

class _FeedModel {
  final String uid;
  final String docId;
  final String image;
  final String path;
  final Timestamp dateTime;

  const _FeedModel({
    required this.uid,
    required this.docId,
    required this.image,
    required this.path,
    required this.dateTime,
  });
  factory _FeedModel.fromFirestore(Map<String, dynamic> json) {
    return _FeedModel(
      uid: json["uid"],
      docId: json["docId"],
      image: json["image"],
      path: json["path"],
      dateTime: json["dateTime"],
    );
  }
  Map<String, dynamic> toFirestore() => {
        "uid": uid,
        "docId": docId,
        "image": image,
        "path": path,
        "dateTime": dateTime,
      };
}

Upload Image To Storage

자 먼저 이미지를 업로드 하고 업로드된 이미지의 Url 주소를 가지고와 Firestore에 저장하는 코드이다.

이미지르 Storage에 업로드 하고 업로드된 이미지의 Url 주소와 저장된 경로를 리턴하는 함수를 만들었다.

Future<Map<String, String>?> _imagePickerToUpload() async {
    if (Platform.isIOS) {
      await Permission.photosAddOnly.request();
    }
    final String _dateTime = DateTime.now().millisecondsSinceEpoch.toString();
    ImagePicker _picker = ImagePicker();
    XFile? _images = await _picker.pickImage(source: ImageSource.gallery);
    if (_images != null) {
      String _imageRef = "feed/${_uid}_$_dateTime";
      File _file = File(_images.path);
      await FirebaseStorage.instance.ref(_imageRef).putFile(_file);
      final String _urlString =
          await FirebaseStorage.instance.ref(_imageRef).getDownloadURL();
      return {
        "image": _urlString,
        "path": _imageRef,
      };
    } else {
      return null;
    }
  }

Image Picker를 사용해서 선택된 이미지를 Storage에 업로드 하고, Firestore로 저장하기 위한 기능이다.

Map<String, String>? _images = await _imagePickerToUpload();
	if (_images != null) {
		await _toFirestore(_images);
     }

Create Firestore

업로드된 이미지 주소와 경로를 받아와 Firebase에 그대로 저장하자, 저장할 때 해당 document ID 값과, 사용자의 고유 UID, 현재 DateTime을 함께 저장하였다.

Future<void> _toFirestore(Map<String, String> images) async {
    try {
      DocumentReference<Map<String, dynamic>> _reference =
          FirebaseFirestore.instance.collection("feed").doc();
      await _reference.set(_FeedModel(
        uid: _uid,
        docId: _reference.id,
        image: images["image"].toString(),
        path: images["path"].toString(),
        dateTime: Timestamp.now(),
      ).toFirestore());
    } on FirebaseException catch (error) {
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text(error.message ?? "")));
    }
  }

Read Firestore

해당 페이지 첫 진입시 Firestore에 저장된 Feed를 가져오는 코드이다. 정렬 기준을 dateTime을 기준으로 정렬하도록 하여 데이터를 가져온다.

  Future<void> _getFeed({bool isRefresh = false}) async {
    if (isRefresh) {
      _feed.clear();
    }
    QuerySnapshot<Map<String, dynamic>> _snapshot = await FirebaseFirestore
        .instance
        .collection("feed")
        .orderBy("dateTime", descending: true)
        .get();
    setState(() {
      _feed = _snapshot.docs
          .map((e) => _FeedModel.fromFirestore(e.data()))
          .toList();
    });
  }

Delete Firestore

삭제를 위해 Object에 저장된 path를 통해 Storage에 저장된 이미지를 삭제하고, documentId를 사용하여 Firestore document를 삭제해 주면 된다.

Future<void> _deleteFeed(_FeedModel data) async {
    await FirebaseStorage.instance.ref(data.path).delete();
    await FirebaseFirestore.instance
        .collection("feed")
        .doc(data.docId)
        .delete();
    setState(() {
      _feed.remove(data);
    });
  }

Result

Git

https://github.com/boglbbogl/flutter_velog_sample/blob/main/lib/firebase/storage/firebase_storage_screen.dart

마무리

Firestore, Realtime 데이터베이스 사용 방법에 이어서 Storage 저장소 사용 방법에 대해서도 살펴보았다.

Storage 저장소는 속도도 느리고, 쿼리를 다양하게 지원하지 않고 있어서 Storage에는 컨텐츠만을 저장하고, Storage에 직접 접근하는 것보다 Firestore, Realtime 등의 데이터베이스에 url 주소만 넣어서 사용해야 하는 것을 꼭 기억하자.

profile
Flutter Developer

0개의 댓글