👨💻 안녕하세요, 윌리 프로젝트의 윌리 입니다.
Flutter 에서 MVVM 디자인 패턴을 적용할 경우, 프로젝트 폴더 구조는 어떻게 구성하는 것이 효율적인지 간략히 알아보았습니다.
질문 >
Flutter 에서 MVVM 디자인 패턴을 적용할 경우, 프로젝트 폴더 구조는 어떻게 구성하는 것이 효율적인가요?
답변 >
Flutter에서 MVVM (Model-View-ViewModel) 디자인 패턴을 적용하면 UI, 비즈니스 로직, 데이터 로직을 분리하여 코드의 가독성과 유지보수성을 높일 수 있습니다. 프로젝트 폴더 구조를 체계적으로 구성하면 작업의 효율성을 높이고, 확장성과 재사용성을 보장할 수 있습니다.
아래는 Flutter MVVM 패턴을 적용한 효율적인 프로젝트 폴더 구조와 설명입니다.
추천 폴더 구조
lib/
├── models/
├── views/
├── viewmodels/
├── services/
├── repositories/
├── utils/
├── widgets/
├── main.dart
폴더별 역할 및 구성
1. models/
역할:
- 데이터의 구조를 정의합니다.
- API 응답 데이터 또는 로컬 데이터의 모델을 포함합니다.
구성:
- DTO(Data Transfer Object)와 같은 데이터 클래스를 정의합니다.
- JSON 데이터를 다루기 위한 fromJson/toJson 메서드 포함.
예제 (models/user.dart):
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
};
}
}
2. views/
역할:
- UI를 정의합니다.
- Flutter 위젯을 사용해 화면의 레이아웃과 디자인을 구현합니다.
- View는 사용자의 입력을 처리하며 ViewModel과 데이터를 연결합니다.
구성:
- 각 화면별로 디렉토리를 생성하거나 파일로 관리합니다.
예제 (views/home_screen.dart):
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../viewmodels/home_viewmodel.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final viewModel = context.watch<HomeViewModel>();
return Scaffold(
appBar: AppBar(title: Text('Home Screen')),
body: Center(
child: viewModel.isLoading
? CircularProgressIndicator()
: ListView.builder(
itemCount: viewModel.users.length,
itemBuilder: (context, index) {
final user = viewModel.users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => viewModel.fetchUsers(),
child: Icon(Icons.refresh),
),
);
}
}
3. viewmodels/
역할:
- View와 Model 사이의 중간 역할을 합니다.
- 비즈니스 로직 및 상태 관리를 처리합니다.
- 상태 관리 라이브러리(Riverpod, Provider, etc.)를 통해 View와 상태를 연결합니다.
구성:
- 각 화면(View)에 대응하는 ViewModel을 생성합니다.
예제 (viewmodels/home_viewmodel.dart):
import 'package:flutter/material.dart';
import '../models/user.dart';
import '../services/user_service.dart';
class HomeViewModel extends ChangeNotifier {
final UserService _userService = UserService();
bool _isLoading = false;
List<User> _users = [];
bool get isLoading => _isLoading;
List<User> get users => _users;
void fetchUsers() async {
_isLoading = true;
notifyListeners();
_users = await _userService.fetchUsers();
_isLoading = false;
notifyListeners();
}
}
4. services/
역할:
- API 호출, 데이터베이스 액세스, 로컬 저장소 등 외부 데이터 소스를 처리합니다.
구성:
- HTTP 클라이언트(dio, http)를 활용한 API 호출 로직 구현.
예제 (services/user_service.dart):
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../models/user.dart';
class UserService {
Future<list> fetchUsers() async {
final response = await http.get(Uri.parse('<https://jsonplaceholder.typicode.com/users>'));
if (response.statusCode == 200) {
final List data = json.decode(response.body);
return data.map((json) => User.fromJson(json)).toList();
} else {
throw Exception('Failed to load users');
}
}
}
</list
5. repositories/
역할:
- 서비스 계층과 ViewModel 사이에서 데이터를 조합하거나 추가 로직을 수행합니다.
- ViewModel이 데이터 소스(Service, Local Storage 등)를 모르게 추상화합니다.
구성:
- 데이터와 서비스 간의 인터페이스를 관리.
예제 (repositories/user_repository.dart):
import '../models/user.dart';
import '../services/user_service.dart';
class UserRepository {
final UserService _userService = UserService();
Future<List<User>> getUsers() async {
return await _userService.fetchUsers();
}
}
6. utils/
역할:
- 유틸리티 클래스 및 헬퍼 함수 모음.
구성:
- 날짜 형식 변환, 문자열 처리, 공통 위젯 스타일링 등.
예제 (utils/date_formatter.dart):
String formatDate(DateTime date) {
return "${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}";
}
7. widgets/
역할:
- 재사용 가능한 UI 컴포넌트 관리.
- 여러 화면에서 공통으로 사용되는 위젯.
구성:
- 버튼, 카드, 리스트 등 공통 위젯을 관리.
예제 (widgets/custom_button.dart):
import 'package:flutter/material.dart';
class CustomButton extends StatelessWidget {
final String label;
final VoidCallback onPressed;
CustomButton({required this.label, required this.onPressed});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: Text(label),
);
}
}
MVVM 폴더 구조의 장점
- 유지보수성:
- 각 계층의 역할이 명확히 분리되어 코드를 쉽게 수정하고 확장할 수 있습니다.
- 재사용성:
- 공통 로직(ViewModel, Service, Repository) 및 컴포넌트(View, Widgets)가 재사용 가능.
- 테스트 용이성:
- 각 계층이 독립적이므로 단위 테스트 작성이 용이합니다.
- 확장성:
- 새로운 기능 추가 시 기존 구조를 유지하면서 손쉽게 확장 가능합니다.
- 가독성:
- 프로젝트의 폴더와 파일 구조가 명확하므로, 협업 시 이해도가 높아집니다.
결론
Flutter에서 MVVM 패턴을 적용하면 코드의 유지보수성과 확장성을 높일 수 있습니다. 위와 같은 폴더 구조는 프로젝트 규모와 복잡도에 맞게 수정하여 사용할 수 있으며, ViewModel과 상태 관리 라이브러리(Riverpod, Provider 등)를 함께 활용하면 더욱 효과적인 설계가 가능합니다.
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
🎬 유튜브 채널 🎬
🎬 치지직 채널 🎬
'프로그래밍' 카테고리의 다른 글
👨💻 [Flutter] Flutter 에서 윈도우 창의 최소크기를 지정하려면 어떻게 해야 하나요? (3) | 2024.12.06 |
---|---|
👨💻 [Flutter] Flutter 에서 Scaffold 클래스는 무엇이며, 어떻게 활용할 수 있나요? (4) | 2024.12.06 |
👨💻 [Unreal] 언리얼 엔진 에서 입력 콘텍스트 (또는 입력 매핑 콘텍스트) 는 무엇이며, 어떻게 활용할 수 있나요? (3) | 2024.12.06 |
👨💻 [Flutter] Flutter에서 CEF 를 직접 통합하려면 어떻게 해야 하나요? (4) | 2024.12.05 |
👨💻 [Flutter] Flutter 에서 Chromium 을 활용하려면 어떻게 해야 하나요? (3) | 2024.12.05 |