카테고리 없음

2025.11.11 (Flutter + Riverpod + Freezed + Hook 기반 구조 정리)

kwontete 2025. 11. 11. 21:11

오늘의 핵심 학습 키워드

개념설명적용 포인트예시
Freezed 불변(Immutable) 데이터 모델을 쉽게 정의하는 코드 생성기 ToDoModel 클래스 생성 시 copyWith, fromJson, toJson 자동화 ToDoModel 모델 생성
Riverpod (AsyncNotifier) 상태 비동기 관리 (Firebase 연동, CRUD 등) HomeViewModel에서 Firebase 데이터 비동기 로드 및 업데이트 ref.watch(homeViewProvider)
copyWith() 데이터 일부만 변경할 때 사용 즐겨찾기/완료 토글 시 기존 상태 복제 toDo.copyWith(isFavorite: !toDo.isFavorite)
HookConsumerWidget / useState / useTextEditingController State를 따로 만들지 않고도 Hook으로 상태 제어 AddToDoDialog의 텍스트 입력, focus 상태 등 관리 final controller = useTextEditingController()
Firebase Firestore CRUD Firestore 데이터 읽기/쓰기/삭제 Repository에서 DB 접근 로직 분리 ToDoRepository

Freezed를 통한 모델 구조 개선

  • 기존 수동 작성하던 모델(ToDoModel)을 @freezed로 대체.
  • 자동 생성되는 copyWith, fromJson, toJson을 통해 코드량 감소.
  • 명시적 타입 (Map<String, Object?>)으로 수정하여 타입 에러 해결.

📄 to_do_model.dart

 
@freezed class ToDoModel with _$ToDoModel { const factory ToDoModel({ required String id, required String title, String? description, @Default(false) bool isFavorite, @Default(false) bool isDone, }) = _ToDoModel; factory ToDoModel.fromJson(Map<String, Object?> json) => _$ToDoModelFromJson(json); }

Riverpod AsyncNotifier 구조 정립

  • Firestore 데이터 비동기 호출을 위한 AsyncNotifier<List<ToDoModel>> 사용.
  • 상태 변경 시마다 AsyncData(await getToDos())로 최신 데이터 반영.
  • copyWith() 덕분에 데이터 토글 간결하게 구현 가능.

📄 home_view_model.dart

 
class HomeViewModel extends AsyncNotifier<List<ToDoModel>> { final ToDoRepository repo = ToDoRepository(); @override Future<List<ToDoModel>> build() async => await repo.getToDos(); Future<void> toggleFavorite({required ToDoModel toDo}) async { final updated = toDo.copyWith(isFavorite: !toDo.isFavorite); await repo.updateToDo(updated); state = AsyncData(await repo.getToDos()); } }

HookConsumerWidget으로 AddToDoDialog 리팩토링

  • 기존 StatefulWidget → HookConsumerWidget으로 전환.
  • useState, useTextEditingController, useFocusNode로 훨씬 간결한 상태 관리.
  • setState() 제거 가능, 훅 기반 반응형 상태 전환 구현.

📄 add_to_do_dialog.dart

 
class AddToDoDialog extends HookConsumerWidget { const AddToDoDialog({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final titleController = useTextEditingController(); final descController = useTextEditingController(); final focusNode = useFocusNode(); final isFavorite = useState(false); final isDescription = useState(false); void save() { final title = titleController.text.trim(); if (title.isEmpty) return; final toDo = ToDoModel( id: '', title: title, description: descController.text.trim().isEmpty ? null : descController.text, isFavorite: isFavorite.value, ); Navigator.of(context).pop(toDo); } return Column( children: [ TextField(controller: titleController, focusNode: focusNode), if (isDescription.value) TextField(controller: descController, maxLines: null), Row( children: [ IconButton( icon: Icon(isFavorite.value ? Icons.star : Icons.star_border), onPressed: () => isFavorite.value = !isFavorite.value, ), IconButton( icon: Icon(Icons.check), onPressed: save, ), ], ) ], ); } }

ViewModel과 View의 연결

  • HomePage에서 ref.watch(homeViewProvider)로 상태 구독.
  • 각 ToDoView 위젯에 콜백을 전달해 개별 작업 수행 가능.
  • 삭제 기능까지 추가 (onDelete → ref.read(homeViewProvider.notifier).deleteToDo(id: toDo.id)).

핵심 정리

항목배운 점
모델 관리 Freezed로 불변 객체 생성 및 JSON 변환 자동화
상태 관리 AsyncNotifier로 Firestore CRUD 제어
UI 연결 HookConsumerWidget으로 StatefulWidget 대체
코드 효율 copyWith와 useState를 활용한 코드 간결화
실무 감각 Repository 패턴 + ViewModel 조합으로 확장성 확보

마무리 요약

오늘은 단순한 Todo앱이 아니라,
실제 구조화된 MVVM + Hook + Freezed 조합으로
앱의 “아키텍처적 완성도”를 올린 하루였습니다.

특히:

  • Riverpod 구조의 비동기 데이터 흐름 이해
  • Hook을 활용한 State 간결화
  • Freezed를 통한 데이터 모델 자동화
    까지 완벽하게 익힌 날