NotifierProvider은 riverpod에서 상태를 저장, 변경하는 방법으로 추천하는 방식입니다.
RIverpod에서는 State를 다음과 같은 방식으로 관리합니다:
State(상태)란 UI에 변화를 주는 데이터 전반을 말합니다.
이번 예제 에서는 체크박스 기능이 있는 Todo 앱을 만들어 보도록 하겠습니다.
우리가 살펴볼 파일의 구조는 다음과 같습니다:
먼저, Todo 데이터 모델을 만들어 줍니다.
Todo는 id, description, completed의 세가지 멤버 변수를 가지고 있습니다.
import 'package:freezed_annotation/freezed_annotation.dart';
part 'model.freezed.dart';
class Todo with _$Todo {
factory Todo({
required String id,
required String description,
required bool completed,
}) = _Todo;
}
Riverpod를 사용할 때 흔한 패턴 중 하나는 Notifier 또는 StateNotifier 객체를 만들고, 이 객체 자체를 데이터를 조작하는 controller로 사용하는 것입니다.
Controller는 앱에서 비즈니스 로직을 관리하는 역할을 합니다. 비즈니스 로직이란 애플리케이션에서 어떤 일을 하는지를 설명하는 규칙들의 모음입니다.
class TodosNotifier extends Notifier<List<Todo>> {
// 첫번째 순서는 build 함수를 정의하는 것입니다.
// Notifier의 build 함수는 state의 디폴트 값을 제공하는 함수 입니다.
// 이 클래스는 state로 List<Todo>를 만들고 있습니다.
// 앞으로 state는 List<Todo>를 의미한다고 이해하시면 됩니다.
List<Todo> build() {
return [
Todo(
id: '1',
description: 'Finish homework',
completed: false,
),
Todo(
id: '2',
description: 'Go grocery shopping',
completed: false,
),
Todo(
id: '3',
description: 'Walk the dog',
completed: true,
),
];
}
// 위에서 build 함수로 정의된 state값을 변경하는 함수를 직접 정의할 수 있습니다.
// 아래 함수는 state에 새로운 todo를 추가하는 함수이비다.
void addTodo(Todo todo) {
state = [...state, todo];
}
void removeTodo(String todoId) {
state = [
for (final todo in state)
if (todo.id != todoId) todo
];
}
void toggle(String todoId) {
state = [
for (final todo in state)
if (todo.id == todoId)
todo.copyWith(completed: !todo.completed)
else
todo,
];
}
// 마지막으로 이렇게 만든 Notifier를 다른 파일에서 호출 가능하도록 todosProvider를 만들어 줍니다.
final todosProvider = NotifierProvider<TodosNotifier, List<Todo>>(
() {
return TodosNotifier();
},
);
마지막으로 이렇게 완성된 Notifier를 이용해 UI를 변경하는 방법을 확인해 보겠습니다.
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../controller/todo_controller.dart';
import '../models/model.dart';
class TodoPage extends HookConsumerWidget {
const TodoPage({super.key});
Widget build(BuildContext context, WidgetRef ref) {
// todosProvider를 호출하여 앞서 정의한 state를 활용하겠습니다
// ref.watch는 provider를 입력값으로 받고, state를 리턴하는 함수입니다.
// * 앞서 말한 바처럼, 이 곳에서 state란 List<Todo>를 말합니다.
List<Todo> todos = ref.watch(todosProvider);
return Scaffold(
body: ListView(
children: [
for (final todo in todos)
CheckboxListTile(
value: todo.completed,
// ref.watch에 provider가 아니라. provider.notifier를 입력하면
// state가 아닌, state를 변경하는 Notifier를 호출합니다.
// 호출된 notifier에 미리 정의된 toggle method를 사용하여 checkbox를 바꿔줍니다.
onChanged: (value) =>
ref.watch(todosProvider.notifier).toggle(todo.id),
title: Text(todo.description),
),
],
),
);
}
}