본문 바로가기
프로그래밍

👨‍💻 [Flutter] Flutter 에서 MVVM 디자인 패턴을 적용할 경우, 프로젝트 폴더 구조는 어떻게 구성하는 것이 효율적인가요?

by 밝음의기둥 2024. 12. 6.
반응형

👨‍💻 안녕하세요, 윌리 프로젝트의 윌리 입니다.

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 폴더 구조의 장점

  1. 유지보수성:
    • 각 계층의 역할이 명확히 분리되어 코드를 쉽게 수정하고 확장할 수 있습니다.
  2. 재사용성:
    • 공통 로직(ViewModel, Service, Repository) 및 컴포넌트(View, Widgets)가 재사용 가능.
  3. 테스트 용이성:
    • 각 계층이 독립적이므로 단위 테스트 작성이 용이합니다.
  4. 확장성:
    • 새로운 기능 추가 시 기존 구조를 유지하면서 손쉽게 확장 가능합니다.
  5. 가독성:
    • 프로젝트의 폴더와 파일 구조가 명확하므로, 협업 시 이해도가 높아집니다.

결론

Flutter에서 MVVM 패턴을 적용하면 코드의 유지보수성과 확장성을 높일 수 있습니다. 위와 같은 폴더 구조는 프로젝트 규모와 복잡도에 맞게 수정하여 사용할 수 있으며, ViewModel과 상태 관리 라이브러리(Riverpod, Provider 등)를 함께 활용하면 더욱 효과적인 설계가 가능합니다.


"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."


🎬 유튜브 채널 🎬

 

위로그@WiLog

📢 안녕하세요, 위로그@WiLog 시청자 여러분, 저는 윌리 입니다. 📢 위로그@WiLog 는 자기주도학습을 목적으로 라이브 스트리밍을 합니다. 📢 1인 게임 개발을 목표로 Unreal과 Blender를 학습 중입니

www.youtube.com

🎬 치지직 채널 🎬

 

위로그 채널 - CHZZK

지금, 스트리밍이 시작됩니다. 치지직-

chzzk.naver.com


반응형