이번 포스팅은 Flutter에서 MVVM (Model-View-ViewModel) 디자인 패턴을 간결하고 효율적으로 적용할 수 있는 기본 코드를 작성해본다.
이 구조는 Riverpod을 사용하여 상태를 관리하고, View-ViewModel-Model 간의 역할을 명확히 분리하여 재사용성을 높이는 형태야.
🔹 구조 설명
lib/
│── main.dart // 앱 진입점
│── core/ // 공통 유틸리티 및 서비스
│── views/ // UI (View)
│── viewmodels/ // ViewModel (비즈니스 로직)
│── repositories/ // repository
│── datasource/ // 데이터 원본 (Remote data source, Local data source 등)
│── models/ // 데이터 모델
📌 코드 구현
기본적인 MVVM 구조를 따르면서, Riverpod을 활용해 상태 관리를 적용한 코드야.
1️⃣ main.dart (앱의 진입점)
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'views/home_view.dart';
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomeView(),
);
}
}
2️⃣ models/item_model.dart (데이터 모델)
class Item {
final String id;
final String name;
Item({required this.id, required this.name});
factory Item.fromJson(Map<String, dynamic> json) {
return Item(
id: json['id'],
name: json['name'],
);
}
}
3️⃣ repositories/item_repository.dart (데이터 관리)
import '../models/item_model.dart';
class ItemRepository {
// 데이터 저장소 (API or 로컬 DB 연동 가능)
Future<List<Item>> fetchItems() async {
await Future.delayed(Duration(seconds: 1)); // 가짜 딜레이
return [
Item(id: "1", name: "Item 1"),
Item(id: "2", name: "Item 2"),
];
}
}
4️⃣ viewmodels/item_viewmodel.dart (ViewModel)
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/item_model.dart';
import '../repositories/item_repository.dart';
final itemRepositoryProvider = Provider((ref) => ItemRepository());
final itemViewModelProvider = StateNotifierProvider<ItemViewModel, AsyncValue<List<Item>>>(
(ref) => ItemViewModel(ref.watch(itemRepositoryProvider)),
);
class ItemViewModel extends StateNotifier<AsyncValue<List<Item>>> {
final ItemRepository repository;
ItemViewModel(this.repository) : super(const AsyncValue.loading()) {
fetchItems();
}
Future<void> fetchItems() async {
try {
state = const AsyncValue.loading();
final items = await repository.fetchItems();
state = AsyncValue.data(items);
} catch (e) {
state = AsyncValue.error(e, StackTrace.current);
}
}
}
5️⃣ views/home_view.dart (View)
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../viewmodels/item_viewmodel.dart';
class HomeView extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final itemState = ref.watch(itemViewModelProvider);
return Scaffold(
appBar: AppBar(title: Text("MVVM with Riverpod")),
body: itemState.when(
data: (items) => ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].name),
);
},
),
loading: () => Center(child: CircularProgressIndicator()),
error: (err, stack) => Center(child: Text("Error: $err")),
),
);
}
}
✅ 설명
1. Model (데이터 정의)
• models/item_model.dart에서 Item 클래스로 데이터를 정의.
2. Repository (데이터 원본)
• repositories/item_repository.dart에서 데이터를 가져오는 역할 수행 (API 연동 가능).
3. ViewModel (비즈니스 로직)
• viewmodels/item_viewmodel.dart에서 StateNotifierProvider로 상태 관리.
• fetchItems()를 호출하여 데이터를 가져오고, 상태를 업데이트.
4. View (UI)
• views/home_view.dart에서 Riverpod의 ConsumerWidget을 사용해 UI에서 상태를 구독.
🎯 이 패턴의 장점
• 재사용 가능: ViewModel과 Repository를 분리하여 유지보수가 쉬움.
• Riverpod 활용: 명확한 상태 관리로 UI 업데이트가 효율적.
• 깔끔한 구조: 불필요한 Boilerplate를 줄이고, 핵심적인 부분만 유지.
필요하면 더 확장할 수도 있고, Test-Driven Development (TDD)에도 적합하다
'IT > flutter' 카테고리의 다른 글
[flutter] Firebase 인증 예외 오류 코드 모음 (0) | 2025.03.24 |
---|---|
[Flutter] Form 위젯과 validate 사용 - 유효성 검사 프로세스 (0) | 2025.03.18 |
[디자인패턴] Flutter에서 MVVM 패턴 적용하기: 개념부터 예제코드까지 (0) | 2025.01.29 |
[flutter] Container위젯과 DecoratedBox 위젯의 효율적인 사용 (0) | 2025.01.23 |
[상태관리] flutter Riverpod을 이용하여 todo 리스트 만들어 보기 (0) | 2024.12.19 |