NotifierProvider는 Notifier를 수신하고 노출하는 데 사용되는 제공자입니다.
AsyncNotifierProvider는 비동기 Notifier를 수신하고 노출하는 데 사용되는 provider입니다. 비동기적으로 초기화할 수 있는 Notifier입니다.
(Async)NotifierProvider와 함께 Riverpod가 사용자 상호작용에 반응하여 변경될 수 있는 상태를 관리하기 위해 권장하는 솔루션은 (Async)Notifier입니다.
일반적으로 다음과 같은 용도로 사용됩니다:
- 사용자 정의 이벤트에 반응한 후 시간이 지남에 따라 변경될 수 있는 상태를 노출합니다.
- 일부 상태(일명 '비즈니스 로직')를 수정하는 로직을 한 곳에 집중시켜 시간이 지남에 따라 유지 관리성을 개선합니다.
예시로 NotifierProvider를 사용하여 할 일 목록을 구현할 수 있습니다. 이렇게 하면 addTodo와 같은 메서드를 노출하여 UI가 사용자 상호작용에 따라 할 일 목록을 업데이트할 수 있도록 할 수 있습니다:
// 변하지 않는 상태를 선호합니다.
// 구현을 돕기 위해 Freezed와 같은 패키지를 사용할 수도 있습니다.
@immutable
class Todo {
const Todo({
required this.id,
required this.description,
required this.completed,
});
// 모든 프로퍼티는 클래스에서 'final'이어야 합니다.
final String id;
final String description;
final bool completed;
// 할 일은 변경할 수 없으므로 약간 다른 내용으로 할 일을 복제할 수 있는 방법을 구현했습니다.
Todo copyWith({String? id, String? description, bool? completed}) {
return Todo(
id: id ?? this.id,
description: description ?? this.description,
completed: completed ?? this.completed,
);
}
}
// NotifierProvider에 전달할 Notifier 클래스입니다.
// 이 클래스는 "state" 프로퍼티 외부에 상태를 노출해서는 안 되며
// 이는 퍼블릭 getters/property가 없음을 의미합니다!
// 이 클래스의 public 메서드는 UI가 상태를 수정할 수 있게 해줍니다.
class TodosNotifier extends Notifier<List<Todo>> {
// 할 일 목록을 빈 목록으로 초기화합니다.
@override
List<Todo> build() {
return [];
}
// UI가 할 일을 추가할 수 있도록 허용해 봅시다.
void addTodo(Todo todo) {
// 상태는 불변이므로 `state.add(todo)`를 수행할 수 없습니다.
// 대신 이전 항목과 새 항목이 포함된 새 할 일 목록을 만들어야 합니다.
// 여기서 다트의 스프레드 연산자를 사용하면 도움이 됩니다!
state = [...state, todo];
// "notifyListeners" 또는 이와 유사한 함수를 호출할 필요가 없습니다.
// "state ="를 호출하면 필요할 때 자동으로 UI를 다시 빌드합니다.
}
// 할 일 제거 허용하기
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)
//다시 한 번, 상태는 불변이므로 할 일의 복사본을 만들어야 합니다.
// 이를 위해 앞서 구현한 `copyWith` 메서드를 사용하고 있습니다.
todo.copyWith(completed: !todo.completed)
else
// other todos are not modified
todo,
];
}
}
// 마지막으로 NotifierProvider를 사용하여 UI가 TodosNotifier 클래스와 상호 작용할 수 있도록 합니다.
final todosProvider = NotifierProvider<TodosNotifier, List<Todo>>(() {
return TodosNotifier();
});
이제 NotifierProvider를 정의했으므로 이를 사용하여 UI의 할 일 목록과 상호작용할 수 있습니다:
class TodoListView extends ConsumerWidget {
const TodoListView({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// 할 일 목록이 변경되면 위젯을 다시 빌드합니다.
List<Todo> todos = ref.watch(todosProvider);
// 할 일을 스크롤 가능한 목록 보기로 렌더링해 보겠습니다.
return ListView(
children: [
for (final todo in todos)
CheckboxListTile(
value: todo.completed,
// 할 일을 탭하면 완료 상태를 변경합니다.
onChanged: (value) =>
ref.read(todosProvider.notifier).toggle(todo.id),
title: Text(todo.description),
),
],
);
}
}
사용 예로 AsyncNotifierProvider를 사용하여 외부 할 일 목록을 구현할 수 있습니다. 이렇게 하면 addTodo와 같은 메서드를 노출하여 UI가 사용자 상호작용에 따라 할 일 목록을 수정할 수 있습니다:
// 불변 상태를 사용하는 것이 좋습니다.
// 구현을 돕기 위해 Freezed와 같은 패키지를 사용할 수도 있습니다.
@immutable
class Todo {
const Todo({
required this.id,
required this.description,
required this.completed,
});
factory Todo.fromJson(Map<String, dynamic> map) {
return Todo(
id: map['id'] as String,
description: map['description'] as String,
completed: map['completed'] as bool,
);
}
// 모든 프로퍼티는 클래스에서 'final'이어야 합니다.
final String id;
final String description;
final bool completed;
Map<String, dynamic> toJson() => <String, dynamic>{
'id': id,
'description': description,
'completed': completed,
};
}
// NotifierProvider에 전달할 Notifier 클래스입니다.
// 이 클래스는 "state" 프로퍼티 외부에 상태를 노출해서는 안 되며
// 이는 퍼블릭 getters/property가 없음을 의미합니다!
// 이 클래스의 public 메서드는 UI가 상태를 수정할 수 있게 해줍니다.
class AsyncTodosNotifier 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();
});
}
}
// 마지막으로 NotifierProvider를 사용하여 UI가 TodosNotifier 클래스와 상호 작용할 수 있도록 합니다.
final asyncTodosProvider =
AsyncNotifierProvider<AsyncTodosNotifier, List<Todo>>(() {
return AsyncTodosNotifier();
});
이제 AsyncNotifierProvider를 정의했으므로 이를 사용하여 UI의 할 일 목록과 상호작용할 수 있습니다:
class TodoListView extends ConsumerWidget {
const TodoListView({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// 할 일 목록이 변경되면 위젯을 다시 빌드합니다.
final asyncTodos = ref.watch(asyncTodosProvider);
// 할 일을 스크롤 가능한 목록 보기로 렌더링하겠습니다.
return asyncTodos.when(
data: (todos) => ListView(
children: [
for (final todo in todos)
CheckboxListTile(
value: todo.completed,
// 할 일을 탭하면 완료 상태를 변경합니다.
onChanged: (value) =>
ref.read(asyncTodosProvider.notifier).toggle(todo.id),
title: Text(todo.description),
),
],
),
loading: () => const Center(
child: CircularProgressIndicator(),
),
error: (err, stack) => Text('Error: $err'),
);
}
}
'Flutter > Package' 카테고리의 다른 글
Flutter Riverpod All Providers (5-4) - FutureProvider (0) | 2023.07.17 |
---|---|
Flutter Riverpod All Providers (5-3) - StateNotifierProvider (0) | 2023.07.16 |
Flutter Riverpod All Providers (5-1) - Provider (0) | 2023.07.16 |
Flutter Riverpod 상태관리 (4) - Code generation 대해 (0) | 2023.07.16 |
Flutter Riverpod 상태관리 (3) - Provider 유저를 위한 Riverpod (0) | 2023.07.15 |