Soojeong Lee

2025. 8. 3.

Soojeong Lee

2025. 8. 3.

Soojeong Lee

2025. 8. 3.

사진 업로드 작업하기

사진 업로드 작업하기

사진 업로드 작업하기

Firebase Storage + Firestore

연동 작업은 다 했으니 이제 CRUD 동작을 조금씩 작업하고 있다.

기본적으로 상태 관리 라이브러리는 riverpod를 쓰고 있다.

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final firebaseStorageProvider = Provider((ref) => FirebaseStorage.instance);
final firebaseFirestoreProvider = Provider((ref) => FirebaseFirestore.instance);
final firebaseAuthProvider = Provider((ref) => FirebaseAuth.instance);


사진 추가 하는 동작에 두가지 비즈니스 로직이 돌아간다.

  1. 사진을 업로드한다.

  2. 사진 객체를 추가한다.

import 'dart:io';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:uuid/uuid.dart';

import '../../models/media_memory.model.dart';
import 'firebase.provider.dart';

final mediaMemoryProvider =
    StateNotifierProvider<MediaMemoryController, AsyncValue<List<MediaMemory>>>(
      (ref) => MediaMemoryController(
        ref.watch(firebaseStorageProvider),
        ref.watch(firebaseFirestoreProvider),
        ref.watch(firebaseAuthProvider),
      ),
    );

class MediaMemoryController
    extends StateNotifier<AsyncValue<List<MediaMemory>>> {
  MediaMemoryController(this._storage, this._firestore, this._auth)
    : super(const AsyncValue.data([]));

  final FirebaseStorage _storage;
  final FirebaseFirestore _firestore;
  final FirebaseAuth _auth;

  Future<void> createMany({
    required String lostPersonId,
    required List<File> files,
  }) async {
    state = const AsyncValue.loading();

    final List<MediaMemory> uploadedMemories = [];
    final userId = _auth.currentUser?.uid;

    try {
      for (final file in files) {
        final fileName = const Uuid().v4();
        final ref = _storage.ref('memories/$userId/$lostPersonId/$fileName');

        final task = await ref.putFile(file);
        final url = await task.ref.getDownloadURL();
        final memory = MediaMemory(url: url, createdAt: DateTime.now());

        final docRef = _firestore.collection('lost_persons').doc(lostPersonId);
        await docRef.update({
          'photos': FieldValue.arrayUnion([memory.toJson()]),
        });

        uploadedMemories.add(memory);
      }

      state = AsyncValue.data(uploadedMemories);
    } catch (e, st) {
      state = AsyncValue.error(e, st);
    }
  }
}

위 동작을 보면, Firebase Storage에 사진을 업로드한 다음에, 해당 url을 기반으로 객체를 생성한다.

해당 객체를 Firestore에 저장을 해주어 관계를 맺어주는 동작이 들어간다. 이렇게 사진 업로드가 잘 된 것을 볼 수 있다. 갤러리 형태의 사진 프리뷰는 url으로 불러오면 보인다.

itemBuilder: (context, i) {
      final photo = person.photos[i];
      return ClipRRect(
        borderRadius: BorderRadius.circular(8),
        child: Image.network(
          photo.url,
          fit: BoxFit.cover,
          errorBuilder: (context, error, stackTrace) =>
              Container(
                color: colorScheme.outline,
                child: const Icon(Icons.broken_image),
              ),
          loadingBuilder: (context, child, progress) {
            if (progress == null) return child;
            return Container(
              color: colorScheme.outline,
              child: const Center(
                child: CircularProgressIndicator(
                  strokeWidth: 2,
                ),
              ),
            );
          },
        ),
      );

영상 프리뷰의 경우 아직 시작을 못했다.


조금 정신 없음…

이렇게 모든 논리를 클라이언트쪽으로만 하다보니까 서버에서 처리했던 다양한 형태가 머릿속에 꼬이기도 하고, Firestore은 별도의 ORM 같은게 직관적으로 안보이다 보니까 조금 정신 없다.

객체 형태랑 테이블 구조를 다시 리팩토링 한다음에 이어 작업을 해야겠다.

Comments

Comments

Comments

Create a free website with Framer, the website builder loved by startups, designers and agencies.