Flutter

2024.11. 13 MVVM 패턴과 상태 관리 Provider 라이브러리를 사용해보기 (코드 발전 시키기 2)

정훈5 2024. 11. 13. 11:26

 

pubspec.yaml

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.6
  provider: ^6.1.2

 

 

 

todo.dart

더보기
// Todo 모델 클래스 - 할 일 데이터를 정의 
class Todo {

  final String id;
  final String title;

  // Alt + insert
  Todo({required this.id, required this.title});
}

 

todo_view_model.dart

더보기
// ViewModel 클래스 - 상태와 로직을 담당한다.

import 'package:flutter/material.dart';
import 'package:my_mvvm_v01/start04/models/todo.dart';


// ChangeNotifier 상속 한다.
class TodoViewModel extends ChangeNotifier {

  // 데이터가 필요하다.
  List<Todo> todos = [];
  
  // 할 일을 추가하는 기능
  void addTodo(String title) {
    final newTodo = Todo(id: DateTime.now().toString(), title: title);
    todos.add(newTodo);
    // 상태 알림 호출
    notifyListeners();
  }
  
  // 할 일을 삭제하는 기능
  void removeTodo(String id) {
    // for문 돌려서 id가 같으면 삭제된다.
    todos.removeWhere( (todo) => todo.id == id);
    // UI에 상태가 변경되었다고 알림
    notifyListeners();
  }


}

 

todo_screen.dart

더보기
import 'package:flutter/material.dart';
import 'package:my_mvvm_v01/start04/view_models/todo_view_model.dart';
import 'package:provider/provider.dart';

// MaterialApp 앱 안에서 외부 라이브러리(프로바이더) 위젯을 감싸 주어야 한다.
void main() => runApp(
      MaterialApp(
        // (_) => TodoViewModel() -> 매개변수를 사용하지 않을거면 _ 를 선언한다.
        home: ChangeNotifierProvider(
          create: (_) => TodoViewModel(),
          builder: (context, child) {
            return TodoScreen();
          },
        ),
      ),
    );

class TodoScreen extends StatelessWidget {
  TodoScreen({super.key});

  final TextEditingController _controller = TextEditingController();


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('MVVM provider Todo List'),
      ),
      body: Column(
        children: [
          // 입력 필드 만들기
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: InputDecoration(labelText: '작업을 입력 하시오'),
                  ),
                ),
                IconButton(
                  onPressed: () {
                    // 여기에서 뷰 모델 클래스를 가져오자 --> DI 처리
                    final todoViewModel = Provider.of<TodoViewModel>(context, listen: false);
                    if(_controller.text.isNotEmpty) {
                       todoViewModel.addTodo(_controller.text);
                       _controller.clear();
                      }
                    },
                  icon: Icon(Icons.add),
                )
              ],
            ),
          ),
          // 아래에 할일 목록 표시 구성
          // 아래에 할일 목록 표시 구성
          Expanded(
            child: Consumer<TodoViewModel>(
              builder: (context, todoViewModel, child) {
                return ListView.builder(
                  itemCount: todoViewModel.todos.length,
                  itemBuilder: (context, index) {
                    // 뷰모델에 있는 자료구조 안에 각 인덱스에 맵핑된 객체 Todo인스턴스 하나
                    final todo = todoViewModel.todos[index];
                    return ListTile(
                      title: Text(todo.title),
                      trailing: IconButton(
                        icon: Icon(Icons.delete),
                        onPressed: () => todoViewModel.removeTodo(todo.id),
                      ),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

 

 

 

statelessWidget으로 작성해도 괜찮은 이유는 상태 관리를 ViewModel이 담당하기 때문입니다.

Provider와 ChangeNotifier를 통해 ViewModel이 상태 변화를 관리하고 UI에 반영하기 때문에, UI 위젯이 반드시 StatefulWidget일 필요가 없습니다

정리

  • StatefulWidget일 필요가 없는 이유: ChangeNotifier를 통해 ViewModel이 상태를 관리하고, UI가 자동으로 알림을 받기 때문에, UI 자체가 상태를 가지지 않아도 됩니다.
  • UI 업데이트 범위 제한: 단, Consumer 위젯으로 변경이 필요한 위젯만 감싸, 필요한 부분만 다시 빌드하게 만듭니다. 이 방식은 성능 최적화에도 유리합니다.