플러터로 앱을 개발하다 보면 파일이 점점 많아지고, 코드가 엉키기 시작하면서 “이거 정리 좀 해야겠는데…?“라는 생각이 들곤 합니다. 특히 프로젝트가 커지면 커질수록 뷰와 비즈니스 로직이 얽히고설켜 유지보수가 어려워지기 쉬워요. 이럴 때 MVVM(Model-View-ViewModel) 아키텍처는 개발의 방향성과 안정성을 잡아주는 구조적 해답이 되어줍니다. 여기에 Riverpod 같은 전역 상태 관리 툴을 곁들이면, UI 업데이트와 상태 동기화도 훨씬 간결하게 관리할 수 있어요.
이번 글에서는 플러터 MVVM 아키텍처의 기본 개념부터 Riverpod과 함께 어떻게 구조화하고, 파일 트리를 어떻게 나누면 좋을지, 그리고 실질적으로 ViewModel을 어떻게 구성하는지까지 살펴보겠습니다.
MVVM 아키텍처란?
MVVM은 Model - View - ViewModel로 구성된 아키텍처 패턴입니다. 안드로이드나 WPF 등에서 많이 사용되던 구조이지만, 플러터에서도 충분히 활용할 수 있습니다.
- Model: 앱의 데이터 구조를 담당합니다. API로 받아온 데이터를 구조화하거나, 데이터를 저장하고 가공하는 역할을 합니다.
- View: 사용자가 실제로 보는 UI입니다. 가능한 한 로직을 배제하고, 화면에 어떤 요소를 보여줄지를 정의하는 역할만 해야 합니다.
- ViewModel: Model과 View를 연결해주는 중간 관리자입니다. 사용자의 인터랙션을 받아서 필요한 데이터를 불러오거나 가공하고, 그 결과를 상태로 View에 전달합니다.
플러터는 기본적으로 선언형 UI이기 때문에, ViewModel에서 상태를 관리하고 notify하는 흐름이 굉장히 자연스럽습니다. 여기에 Riverpod을 활용하면 의존성 주입과 상태 공유가 쉬워지고 테스트도 수월해지죠.
MVVM 구조를 플러터에서 활용하면 얻을 수 있는 장점은 다음과 같습니다.
- 로직과 UI의 분리로 테스트 용이성 향상
- 명확한 책임 분리로 코드 가독성 및 유지보수성 향상
- 전역 상태를 다루는 데 있어서 Riverpod와의 궁합이 좋음
그럼 이제 이 아키텍처에 맞춰 실제로 어떻게 파일 구조를 나누고 Riverpod과 함께 구성할 수 있는지 알아볼게요.
MVVM 구조에 따른 파일트리 구성과 역할 분리
MVVM 아키텍처를 플러터에 적용할 때, 가장 먼저 신경 써야 할 것은 명확한 디렉토리 구조입니다. 프로젝트가 커질수록 ‘어디에 어떤 코드를 둬야 할까’라는 고민이 생기기 마련이기 때문에, 처음부터 깔끔한 구조를 잡아두는 것이 나중을 위해서도 좋습니다.
아래는 실제로 많이 사용하는 기본적인 파일트리 구성 예시입니다.
lib/
├── ui/
│ ├── pages/
│ │ └── home_page.dart
│ ├── viewmodels/
│ │ └── home_viewmodel.dart
│ └── widgets/
│ └── home_header.dart
├── data/
│ ├── models/
│ │ └── user_model.dart
│ └── repositories/
│ └── user_repository.dart
이 구조에서 각 폴더의 역할은 다음과 같습니다.
- ui/pages: View, 즉 실제 화면을 담당합니다. StatelessWidget이나 ConsumerWidget을 기반으로 하며, 화면 구조를 정의합니다.
- ui/viewmodels: 사용자 인터랙션에 따른 상태를 관리하고, 모델과 UI를 연결해주는 ViewModel들이 위치합니다. 상태 클래스와 로직이 들어갑니다.
- ui/widgets: 특정 페이지의 UI 구성 요소 중 재사용 가능한 컴포넌트들을 별도로 분리합니다.
- data/models: 서버나 로컬 DB에서 주고받는 데이터를 정의하는 모델 클래스들이 위치합니다. 보통 fromJson, toJson 메서드가 포함되죠.
- data/repositories: 실제 데이터를 가져오거나 저장하는 역할을 합니다. API 호출, SharedPreferences, DB 연동 등과 관련된 코드가 들어갑니다.
이렇게 나누면 다음과 같은 장점이 있습니다.
- 기능과 관심사의 분리로 유지보수가 편해짐
- 협업 시 충돌 최소화 (UI, 로직, 데이터 담당자 간 충돌 방지)
- 테스트 코드 작성이 쉬움 (ViewModel, Repository를 따로 테스트 가능)
그럼 실제로 ViewModel을 어떻게 정의하고, Riverpod으로 상태를 어떻게 다루는지 예제와 함께 설명해보겠습니다.
ViewModel 구현 및 전역 상태 관리
MVVM 구조의 핵심 중 하나는 ViewModel입니다. 사용자의 행동에 따라 상태를 업데이트하고, 이 상태가 바뀌면 자동으로 UI에 반영되도록 연결하는 역할을 하죠. 플러터에서는 이를 위해 Riverpod 같은 상태 관리 라이브러리를 함께 사용하는 것이 일반적입니다.
먼저 ViewModel을 만들기 위해선 상태 클래스를 정의해야 합니다.
// 상태 클래스
class CounterState {
final int count;
CounterState(this.count);
}
그다음, Notifier를 상속받은 ViewModel 클래스를 정의합니다.
import 'package:flutter_riverpod/flutter_riverpod.dart';
class CounterViewModel extends Notifier<CounterState> {
@override
CounterState build() {
return CounterState(0); // 초기값
}
void increment() {
state = CounterState(state.count + 1);
}
void reset() {
state = CounterState(0);
}
}
그리고 해당 ViewModel을 앱 전역에서 사용할 수 있도록 Provider로 등록합니다.
final counterProvider = NotifierProvider<CounterViewModel, CounterState>(() => CounterViewModel());
이제 UI에서는 ConsumerWidget 또는 ref.watch를 통해 ViewModel의 상태를 읽고, 메서드를 호출할 수 있습니다.
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final counterState = ref.watch(counterProvider);
final counterVM = ref.read(counterProvider.notifier);
return Scaffold(
body: Center(child: Text('Count: ${counterState.count}')),
floatingActionButton: FloatingActionButton(
onPressed: () => counterVM.increment(),
child: Icon(Icons.add),
),
);
}
}
이런 방식으로 ViewModel은 상태의 ‘중앙집중 관리소’ 역할을 하며, UI는 그저 상태를 구독하고 반응만 하면 됩니다. 복잡한 앱일수록 이러한 구조는 유지보수와 협업에 큰 힘을 발휘하죠.
마치며...
플러터에서 MVVM 아키텍처를 도입하고 Riverpod으로 상태 관리를 한다는 건 단순히 폴더 구조를 나누는 수준이 아닙니다. 관심사를 분리하고, 테스트와 유지보수가 쉬운 아키텍처를 구현하며, 무엇보다 앱의 확장성과 안정성을 높이는 기반이 됩니다. 지금은 간단한 상태 하나라도 MVVM 구조로 설계해보는 습관을 들여보는 걸 추천드려요. 나중에 프로젝트가 커졌을 때, 진가를 느끼게 될 테니까요!
세 줄 요약
- MVVM은 UI, 로직, 데이터 계층을 명확히 나눠 관리할 수 있는 구조예요.
- 플러터에서는 Riverpod을 통해 ViewModel에서 상태를 선언하고 앱 전역에서 관리할 수 있어요.
- 파일 구조를 명확히 하고 ViewModel을 잘 설계하면 협업과 유지보수가 훨씬 쉬워져요.
'IT' 카테고리의 다른 글
Firebase를 연동해보자! (0) | 2025.04.15 |
---|---|
DIO를 활용한 HTTP 통신 (0) | 2025.04.14 |
Dart 데이터 직렬화와 역직렬화 (0) | 2025.04.10 |
바이브 코딩으로 달라지는 개발 워크플로우 (0) | 2025.04.08 |
바이브 코딩의 시대, 무엇이 달라질까? (0) | 2025.04.07 |