말랑한 하루

[Flutter] MVVM 본문

개발/Flutter

[Flutter] MVVM

지수는말랑이 2024. 1. 31. 19:09
반응형

Model, View, ViewModel로 구성하는 소프트웨어 아키텍처 디자인 패턴입니다. 사용자 인터페이스(UI)와 비즈니스 로직을 효과적으로 분리하기 위해 사용됩니다.

 

MVVM 디자인 패턴의 가장 큰 특징은 양방향 데이터 바인딩 방식에 있습니다. Model의 변화가 자동으로 View에 반영되고, View의 사용자 입력이 자동으로 ViewModel과 Model에 전달되어 일관된 상태를 유지할 수 있습니다. 그럼으로써, 코드의 재사용성과 유지보수성을 향상시킬 수 있습니다.

 

또한, ViewModel의 재사용성으로 인해 코드 중복을 최소화 하고 모듈성을 증가시킬 수 있습니다. ViewModel은 View와 Model의 중간 매개체로서 비즈니스 로직에 대한 단위 테스트에 용이합니다. 덕분에 View 또한, UI 테스트에 집중할 수 있습니다.

 

🍒 Model

데이터와 비즈니스 규칙을 관리합니다.

데이터의 상태를 유지하고 조작하며, 데이터베이스 및 외부 API와 상호작용합니다.

애플리케이션의 상태와 동작을 관리하며, 데이터의 유효성 검사와 업데이트를 처리합니다.

 

🍒 View

사용자에게 직접적으로 보여주는 인터페이스를 담당합니다.

사용자의 입력을 받아 ViewModel에 전달하고, Model의 변경 사향을 반영하여 화면을 갱신합니다.

 

🍒 ViewModel

View에 표시할 데이터를 제공하거나 포맷, 가공하는 역할을 진행합니다.

View에서 사용자 입력을 받아 Model에 전달해줍니다. 이렇게 View와 Model 사이의 의존성을 끊어주어 View를 독립적으로 만들어줍니다.

🐇 구현

MVVM 디자인 패턴과 Flutter를 활용해서 TodoList에 대한 예제를 구현하여, 실질적으로 MVVM 디자인 패턴에 해당하는 항목은 무엇인지 살펴보겠습니다.

🥕 Model

class Todo {
  final int id;
  final String title;
  final bool isCompleted;

  Todo({
    required this.id,
    required this.title,
    required this.isCompleted,
  });

  factory Todo.fromJson(Map<String, dynamic> json) {
    return Todo(
      id: json['id'],
      title: json['title'],
      isCompleted: json['completed'],
    );
  }
}

🥕 ViewModel

외부 API를 활용해 todoList를 갱신하고, 사용자 정의 함수 add/remove/toggle를 활용해 비즈니스 로직을 구현합니다.

final todoViewModelProvider = AsyncNotifierProvider<TodoViewModel, List<Todo>>(() {
  return TodoViewModel();
});

class TodoViewModel extends AsyncNotifier<List<Todo>> {
  Future<List<Todo>> _fetchTodo() async {
    final json = await http.get('api/todos');
    final todos = jsonDecode(json) as List<Map<String, dynamic>>;
    return todos.map(Todo.fromJson).toList();
  }

  @override
  Future<List<Todo>> build() async {
    return _fetchTodo();
  }

  Future<void> addTodo(Todo todo) async {
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(() async {
      await http.post('api/todos', todo.toJson());
      return _fetchTodo();
    });
  }

  Future<void> removeTodo(String todoId) async {
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(() async {
      await http.delete('api/todos/$todoId');
      return _fetchTodo();
    });
  }

  Future<void> toggle(String todoId) async {
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(() async {
      await http.patch(
        'api/todos/$todoId',
        <String, dynamic>{'completed': true},
      );
      return _fetchTodo();
    });
  }
}

🥕 View

class TodoListView extends ConsumerWidget {
  const TodoListView({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final asyncTodos = ref.watch(todoViewModelProvider);

    return switch (asyncTodos) {
      AsyncData(:final value) => ListView(
          children: [
            for (final todo in value)
              CheckboxListTile(
                value: todo.completed,
                onChanged: (value) {
                  ref.read(todoViewModelProvider.notifier).toggle(todo.id);
                },
                title: Text(todo.description),
              ),
          ],
        ),
      AsyncError(:final error) => Text('Error: $error'),
      _ => const Center(child: CircularProgressIndicator()),
    };
  }
}

MVVM 디자인 패턴을 적용한다면, 상태 관리 라이브러리를 활용하던 부분이 ViewModel로써 활용된다고 생각하면 편하다. 그러나, MVVM 디자인 패턴을 단독으로 사용했을 때, 외부 API와 연동하고 관리하는 부분을

ViewModel이 강력하게 의존하고 있다.

 

그렇기 때문에, 시스템 아키텍처와 디자인 패턴을 혼합하여 가져올 수 있는 이점들을 충분히 가져오려는 것 같다.

반응형
Comments