Soojeong Lee

2025. 8. 9.

Soojeong Lee

2025. 8. 9.

Soojeong Lee

2025. 8. 9.

중간 리팩토링: 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,
    };


LostPersonMediaMemory는 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들을 주입해준다. LostPersonRepositoryMediaMemoryRepository에서 데이터를 각각 쿼리해오고, 이를 결합해주어 객체에서 사용할 수 있도록 처리해준다.

여기서 중요한건 각 repo는 각각의 테이블에서 쿼리해오고 provider에서 결합해주는 논리가 있단거다. 섞이지 않게 각각의 책임을 가지게끔 마무리 짓는다.

이렇게 되면 깔끔하고 유지보수가 수월한 형태가 마무리 지어진다.

Comments

Comments

Comments

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