티스토리 뷰

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 로 사용하게 했습니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/08   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함