티스토리 뷰
StateNotifierProvider
공식 문서 설명에 따르면 StateNotifierProvider 는StateNotifier를 듣고있는(구독하는) provider 라고 하며
StateNotifier 는 immutable state 를 저장하는 관찰 가능한 클래스 라고 합니다. State 는 StateNotifier 의 상태를 의미합니다.
간단하게 이야기하면 상태의 변화를 계속 들을 수 있는 provider 쯤으로 이해할 수 있습니다.
+ immutable 과 mutable class 의 차이
사용법
공식문서에 간단한 todo list 예제가 있어 가져왔습니다.
@immutable
class Todo {
const Todo({required this.id, required this.description, required this.completed});
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,
);
}
}
우선 immutable 한 클래스를 하나 만듭니다. copyWith 는 해당 객체의 수정을 위한것입니다.
class TodosNotifier extends StateNotifier<List<Todo>> {
TodosNotifier(): super([]);
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,
];
}
}
그리고 StateNotifierProvider에 넣어줄 StateNotifier 를 만들어줍니다.
List는 mutable 한 클래스이지만 state 는 immutable 해야하기때문에 addTodo(), removeTodo() 를 만들때 list.add(), list.removeAt() 과 같은 기능을 사용하지 않고 직접 추가하고 삭제해야합니다.
final todosProvider = StateNotifierProvider<TodosNotifier, List<Todo>>((ref) {
return TodosNotifier();
});
Provider 를 만들어 줍니다. StateNotifierProvider<StateNotifier클래스를 상속한 클래스, StateNotifier 클래스의 State 의 타입>을 넣어 사용해야합니다.
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),
),
],
);
}
}
체크박스 타일을 탭해서 변화가 생기는 순간 onChange 함수에서 todoNotifier 의 toggle 함수를 실행해 todo 객체의 completed 값을 변경합니다. toggle 함수에서 state = ..... 이 실행되며 state 값이 변하면 ref.watch(todosProvider) 에 상태가 변한걸 알려주어 build 함수가 다시 실행되며 페이지가 렌더링 됩니다.
활용
여러가지 방법으로 활용할 수 있지만 서버에서 데이터를 불러올때 객체의 Loading, Error, 정상상태 로 나눠 보여줄 수도 있습니다.
예를 들어 로그인시 유저의 정보가 제대로 들어온상태, 유저 정보를 로딩중인 상태, 유저 정보를 제대로 불러오지 못한 에러 상태로 나눠 해당 상태에 따라 ui 를 다르게 그릴 수 있습니다.
part 'user_model.g.dart';
abstract class UserModelBase {}
class UserModelLoading extends UserModelBase {}
class UserModelError extends UserModelBase {}
@JsonSerializable()
class UserModel extends UserModelBase {
final String id;
final String username;
UserModel({
required this.id,
required this.username,
});
factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json);
}
서버에서 데이터를 json 변환의 편의성을 위해 Json_serialiable 을 사용했습니다.
abstract class 로 base 모델을 만들고 base 모델을 extends 한 로딩, 에러, 정상 세가지 클래스를 만들었습니다.
class UserMeStateNotifier extends StateNotifier<UserModelBase?> {
UserMeStateNotifier()
: super(UserModelLoading()) {
getInfo();
}
Future<void> getInfo() async {
// 유저 확인 로직
// 저장된 토큰 혹은 정보가 이미 있다면 로그인 필요 없이 유저 확인 후 state 에 UserModel 을 넣어줌
}
Future<UserModelBase> login({required String username, required String password}) async {
//로그인 로직
//로그인이 정상적으로 됐다면 state 에 로그인 후 UserModel 을 넣어줌
//에러 발생시엔 state = UserModelError를 넣어줌
}
직접 구현한 코드가 복잡해보여 로직부분은 지우고 가져왔습니다.
StateNotifier가 UserModelBase 를 듣고 있어 로그인 후 결과에 따라 UserModelBase 를 상속한 UserModel / UserModelError / UserModelLoading 등의 다양한 상태를 표현하는 객체로 결과를 받을 수 있고 해당 결과에 따라 UI 를 다르게 보여줄 수 있습니다.
저는 authProvider 를 ChangeNotifierProvider 로 만들어 해당 UserMeStateNotifierProvider 를 듣고,
GoRouter 의 설정과 reditect Logic까지 authProvider에 선언해둔 뒤 authProvider 를 routerProvider를 일반 provider로 만들고 authProvider 를 watch 하며 materialApp.router 에서 routerConfig 로 사용하게 했습니다.
'Flutter' 카테고리의 다른 글
flutter 플러터 - 카카오톡으로 로그인 (1) | 2025.01.03 |
---|---|
flutter 플러터 - inAppWebview onCreateWindow 팝업 닫을때 새로고침 현상 (1) | 2023.12.06 |
Flutter 플러터 - willPopScope 대신 popScope 적용해보기 (1) | 2023.11.25 |