MVVM

김동준·2026년 1월 7일

MVVM

Model (데이터의 정의와 출처)

데이터의 '생김새''가져오는 방법'을 정의한 부분입니다.

Post 클래스: "게시글은 id, 제목, 내용으로 구성된다"라는 데이터의 규격.

PostRepository 클래스: "파이어베이스에서 데이터를 가져오거나 지운다"라는 데이터의 출처.

파일: post.dart, post_repository.dart

ViewModel (상태 관리와 비즈니스 로직)

화면과 데이터를 연결하는 '중간 관리자'입니다. Repository에서 데이터를 받아와 UI가 쓰기 좋게 '상태'로 들고 있는 부분입니다.

HomeViewModel 클래스: Notifier를 상속받아 state(List<Post>)를 관리하는 곳.

homeViewModelProvider: UI가 ViewModel에 접근할 수 있게 해주는 관리자.

파일: home_view_model.dart

View (사용자 인터페이스)

사용자의 눈에 보이는 '화면' 그 자체입니다. 데이터를 직접 가져오지 않고, 오직 ViewModel의 상태만 지켜보다가 화면을 그립니다. (이 부분은 보통 ConsumerWidget으로 만듭니다.)

HomePage 위젯: ListView, Text, Image 등으로 구성된 실제 화면.

특징: ref.watch(homeViewModelProvider)를 사용하여 ViewModel의 데이터를 실시간으로 관찰함.

파일: home_page.dart


[ 사용자 ] 
    |
 (1) 검색어 입력 ("플러터")
    |
    v
 [ HomePage (UI) ]  ---- (2) 검색해줘! ----> [ HomeViewModel (관리자) ]
                                                   |
                                            (3) 데이터 좀 가져와!
                                                   |
                                                   v
                                          [ BookRepository (공장) ]
                                                   |
    <------- (5) "JSON 문자열" 응답 -------- (4) API 요청 (GET)
 [ 네이버 서버 ]                                     |
                                                   |
                                            (6) 가공 단계 (중요!)
                                            - jsonDecode (해체)
                                            - Book.fromJson (조립) 
                                                   |
    <------- (7) List<Book> 반환 ------------------+
    |
    v
 [ HomeViewModel ] 
    |
 (8) state = HomeState(books) <--- "창고에  상품 채움!"
    |
 (9) 알림 (Riverpod) ----------------------> [ HomePage ]
                                               |
                                        (10) 화면 다시 그리기!
                                        (Image.network 출력)

(6) 가공 단계 (중요!)

class PostRepository {
  //
  Future<List<Post>> getAll() async {
    // 1. 파이어스토어 인스턴스 가지고오기
    final firestore = FirebaseFirestore.instance;
    // 2. 컬렉션 참조 만들기
    final collectionRef = firestore.collection('posts');
    // 3. 값 불러오기
    final result = await collectionRef.get();
    final docs = result.docs;
    return docs.map((doc) {
      final map = doc.data();
      doc.id;
      final newMap = {'id': doc.id, ...map};
      return Post.fromJson(newMap);
    }).toList();
  }
}

Future<List<Post>> getAll() async {

Future<List<Post>>: 이 함수는 당장 결과가 나오지 않고, 시간이 조금 걸린 뒤에(Future) Post 객체들이 담긴 리스트(List)를 돌려준다는 뜻입니다.

async: 비동기 처리를 하겠다는 선언입니다. (데이터를 가져올 동안 앱이 멈추지 않게 함)

  final firestore = FirebaseFirestore.instance;

FirebaseFirestore.instance: 파이어베이스 데이터베이스에 접속하기 위한 통로를 여는 명령입니다. "자, 이제 파이어베이스랑 대화할 준비 됐어!"라고 말하는 것과 같습니다

  final collectionRef = firestore.collection('posts');

collection('posts'): 파이어베이스 콘솔에 보면 'posts'라는 이름의 컬렉션(폴더와 비슷함)이 있습니다. 그 특정 폴더를 가리키는 주소록을 만드는 작업입니다.

  final result = await collectionRef.get();

await ... get(): 실제로 서버에 "데이터 줘!"라고 요청하고 기다리는 과정입니다. 결과물resultQuerySnapshot이라는 형태의 큰 뭉텅이로 넘어옵니다.


다음은 가져온 데이터 뭉텅이를 우리가 쓸 수 있는 Post 객체로 바꾸는 핵심 부분입니다.

  final docs = result.docs; // 1. 문서(문서 꾸러미)들만 따로 뽑아냄
  return docs.map((doc) {   // 2. 문서 하나하나를 순회하며(반복문과 비슷) 작업함 
  final map = doc.data(); // 3. 문서 안의 실제 내용(제목, 내용 등)을 Map 형태로 가져옴
  // 4. 중요! 파이어베이스는 '문서 ID'가 데이터 내용물(Map) 밖에 따로 있습니다.
  // 그래서 id와 나머지 내용(map)을 하나로 합쳐주는 과정이 필요합니다.
  final newMap = {
    'id': doc.id,   // 문서의 고유 ID (예: "ABC12345")
    ...map          // 나머지 내용 (title: "안녕", content: "반가워" 등)을 펼쳐서 합침
  };
  // 5. 합쳐진 Map을 사용해서 Post 객체를 생성함
  return Post.fromJson(newMap);
}).toList(); // 6. 가공된 Post 객체들을 다시 리스트 형태로 묶어서 반환함

CRUD 에서 id


Ref: 실제 데이터가 아니라, 데이터를 찾을 수 있는 '주소값' 혹은 '통로'다"라는 것을 명시하기 위해사용

0개의 댓글