guricode

Riverpod, MVVM 구조로 프로젝트를 설계하는 법 본문

앱/Flutter&Dart

Riverpod, MVVM 구조로 프로젝트를 설계하는 법

agentrakugaki 2025. 7. 31. 20:29

시작 전에: MVVM + Riverpod?

Flutter는 자유도가 너무 높아서 그냥 짜면 View, 로직, 상태가 뒤섞이기 쉽다.
그래서 유지보수가 어려워지고, 테스트도 힘들고, 재사용도 안 된다.

그래서 역할을 나눠서(View-Model-ViewModel) 설계하면 깔끔해진다.
그리고 상태관리는 뭐 쓸까 고민하다가 — Riverpod이 구조 잡기에 최고더라.
다양한 Provider로 의존성 주입도 자연스럽고, 테스트도 쉽고, 전역 상태도 잘 관리됨.


1단계 - 모델(models): 데이터는 깨끗하게 보관하자

API 응답값을 맵 그대로 들고 다니면 에러 난다.
그래서 응답값을 Dart 객체로 바꿔서 쓰는 게 기본.

예를 들어 네이버 지역 검색 API에서 받은 응답은 이런 식:

{
  "title": "OO치킨",
  "roadAddress": "서울시 강남구 OO로 123",
  "mapx": "126.123",
  "mapy": "37.456"
}

이걸 Location이라는 모델 클래스로 만들어두면, 이후부터 타입 안정성 + 코드 자동완성까지 얻는다.
→ 딱 하나의 책임만 있는 클래스: 데이터 표현


2단계 - 서비스(services): 외부와 통신하는 창구

여기서 중요한 건 "외부"라는 키워드.
HTTP 요청이나 Firebase 같은 외부 시스템은 언제든지 바뀔 수 있다.
그래서 앱 내부랑 직접 엮지 말고, service 단에서만 통신하게 제한하는 게 맞다.

  • 네이버 API는 NaverApiService

  • Firestore는 FirestoreService

이런 식으로 외부 API마다 파일 하나씩 만들어서 의존성과 변동성을 이 레이어에 가둬놓는다.


3단계 - 레포지토리(repositories): 서비스 가공하고 뷰모델에게 전달

서비스는 잘 만들었지만, 실제 앱에서 쓸 때는 전처리가 필요하다.
예외 처리도 해야 하고, 특정 조건 처리, 데이터 정렬 같은 것도 해야 함.

그래서 repository를 하나 더 둔다.
목표는 ViewModel이 정말 간단한 인터페이스만 갖도록 도와주는 것.

예:

final results = await locationRepository.search("강남 치킨");

이런 한 줄이면 결과가 잘 오도록 내부 로직을 repository가 다 감싸주는 느낌.


4단계 - 뷰모델(viewmodels): 상태(State)와 로직(Action)을 가진 클래스

이제 진짜 핵심이다.
상태를 직접 들고 있고, 사용자의 입력에 따라 상태를 업데이트하는 로직이 여기에 들어간다.

그리고 여기에 Riverpod의 StateNotifier 또는 AsyncNotifier를 쓴다.
그럼 상태 변화를 감지해서 View에 자동으로 알려줄 수 있다.

final locationVMProvider = StateNotifierProvider<LocationVM, AsyncValue<List<Location>>>(...);

ViewModel의 핵심은 'View와 데이터를 연결하는 다리 역할'이다.
View가 직접 로직을 갖지 않도록 책임을 이곳으로 넘기는 게 포인트.


5단계 - 뷰(views): UI만 만들자. 오직 보여주기만.

여기서는 ref.watch()로 상태를 구독하고,
ref.read(...notifier).search()처럼 뷰모델에 명령만 전달한다.

그러면 UI 코드가 정말 깔끔해진다.
로딩 상태면 로딩 표시, 데이터 상태면 리스트 출력, 에러 상태면 에러 메시지.

→ View는 “지시받은 대로 보여주는 역할”만 한다.


정리하며 느낀 점

  • 처음에는 폴더 나누고 Provider 나누는 게 복잡하게 느껴졌다.

  • 그런데 한 번 익숙해지니까, 실제 기능 추가하거나 수정할 때 훨씬 빠르고 안전하다.

  • 구조가 잘 잡혀있으면 버그도 덜 나고, 코드도 보기 좋고, 동료랑 협업도 수월하다.


마지막 한 줄 요약

MVVM 구조로 나누는 이유는 "View, Logic, State를 명확히 분리"해서 유지보수성과 확장성을 높이기 위함이고, Riverpod은 그걸 자연스럽게 가능하게 해주는 도구다.