Flutter/Dart 언어

[Flutter] MVVM + 상태관리

공돌이 출신 개발자 2025. 2. 5. 19:51
728x90
📝 Flutter Riverpod을 활용한 MVVM 패턴 정리

Flutter에서 Riverpod을 활용한 MVVM 패턴을 적용하면 상태 관리를 더욱 간결하고 효율적으로 할 수 있습니다.
이번 포스팅에서는 기본적인 MVVM 패턴을 Riverpod을 활용하여 개선하는 과정을 살펴보겠습니다.


1️⃣ MVVM 패턴이란?

👉 UI(View)와 비즈니스 로직(Model)을 ViewModel을 통해 분리하여 코드의 유지보수성과 테스트 용이성을 높이는 패턴
👉 기존의 setState() 방식 대신 Riverpod을 활용하여 전역 상태 관리


2️⃣ MVVM 패턴 구현

이번 구현에서는 Riverpod을 사용하여 MVVM 패턴을 적용할 것입니다.

📌 구조

  • Model (todo_item.dart): 데이터 모델을 정의.
  • ViewModel (todo_list_view_model.dart): 비즈니스 로직을 담당하고 Riverpod을 활용하여 상태 관리.
  • View (todo_list_view.dart): UI를 관리하며 ViewModel과 상호작용.

📌 Step 1: Model 생성 (todo_item.dart)

👉 할 일(Todo) 항목을 정의하는 데이터 모델

// Model
// 1. 우리가 관리 하고 싶은 데이터 (창고 안에 넣을 상품)
class TodoItem {
  String title;
  bool isDone;

  TodoItem({required this.title, this.isDone = false});

  // 상태 변경 시 불변 객체를 유지하기 위한 copyWith 메서드 추가
  TodoItem copyWith({String? title, bool? isDone}) {
    return TodoItem(
      title: title ?? this.title,
      isDone: isDone ?? this.isDone,
    );
  }
}

 

📌 역할

  • title: 할 일 제목.
  • isDone: 완료 여부.
  • 불변 객체 원칙을 유지하기 위해 copyWith 메서드 추가.

📌 Step 2: ViewModel 생성 (todo_list_view_model.dart)

👉 비즈니스 로직을 담당하며, 상태를 Riverpod으로 관리

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/todo_item.dart';

// ViewModel - 상태 관리 및 비즈니스 로직을 담당
class TodoListViewModel extends Notifier<List<TodoItem>> {
  @override
  List<TodoItem> build() {
    return [];
  }

  // 할 일 추가
  void addItem(String title) {
    state = [...state, TodoItem(title: title)];
  }

  // 할 일 완료 상태 변경 (불변 객체 유지)
  void toggleItem(TodoItem todo) {
    state = state
        .map((item) => item == todo ? item.copyWith(isDone: !item.isDone) : item)
        .toList();
  }
}

// Provider - ViewModel을 Riverpod 상태 관리에 등록
final todoListViewModelProvider =
    NotifierProvider<TodoListViewModel, List<TodoItem>>(
  () => TodoListViewModel(),
);

 

📌 개선된 점

  • Riverpod을 활용하여 상태 관리 (NotifierProvider 사용).
  • state = [...state, TodoItem(title: title)]; → 기존 리스트를 변경하지 않고 새로운 객체를 생성하여 업데이트.
  • toggleItem에서 불변 객체(copyWith)를 유지하여 안전한 상태 관리.

📌 Step 3: View 생성 (todo_list_view.dart)

👉 ViewModel을 사용하여 UI를 업데이트하는 역할

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/todo_item.dart';
import '../view_models/todo_list_view_model.dart';

class TodoListView extends ConsumerWidget {
  final TextEditingController _controller = TextEditingController();

  TodoListView({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // ViewModel의 상태를 감시 (자동 업데이트)
    final todos = ref.watch(todoListViewModelProvider);
    // ViewModel 인스턴스를 가져와서 메서드 실행
    final todoNotifier = ref.read(todoListViewModelProvider.notifier);

    return Flexible(
      child: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: TextField(
              controller: _controller,
              decoration: InputDecoration(
                hintText: 'Enter todo item...',
                suffixIcon: IconButton(
                  onPressed: () {
                    if (_controller.text.isNotEmpty) {
                      todoNotifier.addItem(_controller.text);
                      _controller.clear();
                    }
                  },
                  icon: Icon(Icons.add),
                ),
              ),
            ),
          ),
          SizedBox(height: 16.0),
          Expanded(
            child: ListView.builder(
              itemCount: todos.length,
              itemBuilder: (context, index) {
                final TodoItem item = todos[index];
                return ListTile(
                  title: Text(item.title),
                  trailing: Checkbox(
                    value: item.isDone,
                    onChanged: (value) {
                      todoNotifier.toggleItem(item);
                    },
                  ),
                );
              },
            ),
          )
        ],
      ),
    );
  }
}

 

📌 개선된 점

  • ConsumerWidget을 사용하여 Riverpod 상태를 구독.
  • ref.watch(todoListViewModelProvider) → 자동으로 UI 업데이트.
  • View에서는 ViewModel의 메서드를 호출하는 역할만 수행.

📌 Step 4: 앱 실행 (main.dart)

👉 ProviderScope를 사용하여 Riverpod을 활성화하고 TodoListView를 표시

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_statement_v01/_mvvm/_04/views/todo_list_view.dart';

void main() {
  runApp(ProviderScope(child: TodoApp()));
}

class TodoApp extends StatelessWidget {
  const TodoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: SafeArea(
        child: Scaffold(
          appBar: AppBar(
            title: const Text('TodoList'),
          ),
          body: TodoListView(),
        ),
      ),
    );
  }
}

 

📌 역할

  • ProviderScope를 추가하여 Riverpod 상태 관리 활성화.
  • TodoListView()를 Scaffold 내에 배치하여 UI 구성.

📌 MVVM 패턴 적용 후 개선된 점

개선 사항 기존 방식 (setState()) Riverpod 적용 방식
상태 관리 setState()를 통해 직접 관리 NotifierProvider를 활용하여 전역 상태 관리
비즈니스 로직 위치 View에서 직접 관리 ViewModel에서 관리
UI 업데이트 setState() 호출 시 전체 위젯 재빌드 필요한 부분만 자동 업데이트
불변 객체 유지 기존 객체를 직접 수정 copyWith을 활용하여 상태 변경

 


📝 결론

Riverpod을 활용한 MVVM 패턴의 장점

UI와 비즈니스 로직을 완전히 분리하여 유지보수성이 향상됨.
Riverpod을 통해 전역 상태를 효율적으로 관리하여 코드의 가독성이 향상됨.
View는 UI를 렌더링하는 역할만 수행하고, ViewModel에서 모든 상태 관리를 담당하여 구조가 깔끔해짐.
불변 객체 원칙을 유지하여 예측 가능한 상태 관리 가능.

👉 Riverpod을 활용하면 더 유지보수하기 좋은 구조로 Flutter 애플리케이션을 개발할 수 있습니다! 🚀


MVVM 패턴이 궁금하다면??

 

[Flutter] MVVM TodoList 만들기

📝 Flutter MV 패턴 & MVVM 패턴 정리Flutter에서 MV (Model-View) 패턴과 MVVM (Model-View-ViewModel) 패턴을 활용하여 코드의 유지보수성과 가독성을 높일 수 있습니다.이번 포스팅에서는 MV → MVVM으로 발전하는

seohong.tistory.com

 

 

 

[Flutter] MVVM 패턴에 대해서 알아 보자

📝 Flutter MVVM 패턴 정리Flutter에서 애플리케이션을 개발할 때, 유지보수성과 확장성을 높이기 위해 MVVM (Model-View-ViewModel) 패턴을 적용할 수 있습니다.이번 포스팅에서는 MVVM 패턴의 개념과 코드

seohong.tistory.com