중간 리팩토링: Firestore
중간 리팩토링: Firestore
중간 리팩토링: Firestore



꼬인 관계 풀어주기
일단 모델을 다시 잡아보았다.
class BaseModel { String? id; BaseModel({this.id}); }
import 'package:intl/intl.dart'; import 'base.model.dart'; import 'custom_user.model.dart'; import 'media_memory.model.dart'; import 'memory.model.dart'; import 'written_memory.model.dart'; class LostPerson extends BaseModel { final String name; final String? nickname; final DateTime? dateOfDeath; final String? memorialLocation; final String? profileImageUrl; List<MediaMemory> medias; final List<WrittenMemory> notes; final CustomUser? user; String? _userId; LostPerson({ super.id, required this.name, this.nickname, this.dateOfDeath, this.memorialLocation, this.profileImageUrl, this.user, List<MediaMemory>? medias, List<WrittenMemory>? notes, String? userId, }) : medias = medias ?? [], notes = notes ?? [], _userId = userId; set userId(String? value) => _userId = value; String? get userId => user?.id ?? _userId; String get displayName => nickname ?? name; String? get displayDateOfDeath => dateOfDeath != null ? DateFormat.yMMMMd().format(dateOfDeath!) : null; List<Memory> get memories => [...medias, ...notes]; Map<String, dynamic> toJson() { return { 'name': name, 'nickname': nickname, 'dateOfDeath': dateOfDeath?.toIso8601String(), 'memorialLocation': memorialLocation, 'profileImageUrl': profileImageUrl, 'userId': userId, }; } static LostPerson fromJson({ required String id, required Map<String, dynamic> json, }) {
객체를 생성할 때, 필요한 정보만 보내도록 개선하였다. 즉 관계를 맺는 키 (id)만 보내는 형태.
{ 'name': name, 'nickname': nickname, 'dateOfDeath': dateOfDeath?.toIso8601String(), 'memorialLocation': memorialLocation, 'profileImageUrl': profileImageUrl, 'userId': userId, };
LostPerson
과 MediaMemory
는 1:N 관계다. 하지만 우리의 Firebase는 join을 제공하지 않는다. 따라서 병렬적으로 쿼리를 해오는 수 밖에 없다.
import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_starter/repositories/media_memory.repository.dart'; import 'package:flutter_starter/services/providers/media_memory.provider.dart'; import '../../models/lost_person.model.dart'; import '../../repositories/lost_person.repository.dart'; import 'firebase.provider.dart'; final lostPersonRepositoryProvider = Provider( (ref) => LostPersonRepository(ref.watch(firebaseFirestoreProvider)), ); final lostPersonProvider = StateNotifierProvider<LostPersonController, AsyncValue<List<LostPerson>>>( (ref) => LostPersonController( auth: ref.watch(firebaseAuthProvider), repo: ref.watch(lostPersonRepositoryProvider), mediaRepo: ref.watch(mediaMemoryRepositoryProvider), ), ); class LostPersonController extends StateNotifier<AsyncValue<List<LostPerson>>> { LostPersonController({ required FirebaseAuth auth, required LostPersonRepository repo, required MediaMemoryRepository mediaRepo, }) : _auth = auth, _repo = repo, _mediaRepo = mediaRepo, super(const AsyncValue.loading()); final FirebaseAuth _auth; final LostPersonRepository _repo; final MediaMemoryRepository _mediaRepo; LostPerson? getById(String id) { return state.maybeWhen( data: (list) => list.firstWhere((p) => p.id == id), orElse: () => null, ); } Future<void> fetchAll() async { final uid = _auth.currentUser?.uid; try { final lostPersons = await _repo.fetchByUserId(uid!); final mediaLists = await Future.wait( lostPersons.map((p) => _mediaRepo.fetchByLostPersonId(p.id!)), ); for (var i = 0; i < lostPersons.length; ++i) { lostPersons[i].medias = mediaLists[i]; } state = AsyncValue.data(lostPersons); } catch (e, st) { state = AsyncValue.error(e, st); } } Future<void> createOne(LostPerson person) async { try { final uid = _auth.currentUser?.uid; person.userId = uid; await _repo.createOne(person); await fetchAll(); } catch (e, st) { state = AsyncValue.error(e, st); } } }
여기서 우리는 필요한 repo들을 주입해준다. LostPersonRepository
와 MediaMemoryRepository
에서 데이터를 각각 쿼리해오고, 이를 결합해주어 객체에서 사용할 수 있도록 처리해준다.
여기서 중요한건 각 repo는 각각의 테이블에서 쿼리해오고 provider에서 결합해주는 논리가 있단거다. 섞이지 않게 각각의 책임을 가지게끔 마무리 짓는다.
이렇게 되면 깔끔하고 유지보수가 수월한 형태가 마무리 지어진다.
Next Article
Next Article
Next Article
Comments
Comments
Comments