riverpod read listen

topics
types 이론 실습
tags
references anpigon.tistory.com/359 riverpod.dev/docs/essentials/reading

Riverpod read vs watch vs listen

Riverpod에서 Provider 값을 읽는 세 가지 방법의 차이다.

핵심 차이

메소드 구독 리빌드 사용 시점
watch O O UI에 상태 반영
read X X 이벤트 핸들러 (일회성)
listen O X 사이드 이펙트 처리

watch - UI 구독

상태가 변경될 때마다 위젯을 리빌드한다.

class CounterScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 상태 변경 시 위젯 리빌드
    final count = ref.watch(counterProvider);

    return Text('Count: $count');
  }
}

언제 쓸까: UI에 상태를 표시할 때. 상태가 바뀌면 화면도 바뀌어야 할 때.

watch 주의사항

// 나쁜 예 - 이벤트 핸들러에서 watch
onPressed: () {
  final count = ref.watch(counterProvider);  // 빌드 중이 아닌데 watch 사용
}

// 좋은 예
onPressed: () {
  final count = ref.read(counterProvider);
}

read - 일회성 읽기

구독하지 않고 현재 값만 읽는다. 리빌드 없음.

class CounterScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ElevatedButton(
      onPressed: () {
        // 일회성으로 값 읽기
        final count = ref.read(counterProvider);
        print('Current: $count');

        // notifier 접근
        ref.read(counterProvider.notifier).increment();
      },
      child: Text('Increment'),
    );
  }
}

언제 쓸까: 버튼 클릭, 제스처 등 이벤트 핸들러에서. 상태를 변경할 때.

read 주의사항

// 나쁜 예 - build에서 read
Widget build(BuildContext context, WidgetRef ref) {
  final count = ref.read(counterProvider);  // 상태 변경 감지 안됨!
  return Text('Count: $count');
}

// 좋은 예
Widget build(BuildContext context, WidgetRef ref) {
  final count = ref.watch(counterProvider);
  return Text('Count: $count');
}

listen - 사이드 이펙트

상태 변경을 구독하지만 리빌드하지 않고 콜백만 실행한다.

class CounterScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 상태 변경 시 콜백 실행 (리빌드 X)
    ref.listen<int>(counterProvider, (previous, next) {
      if (next >= 10) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('10 reached!')),
        );
      }
    });

    final count = ref.watch(counterProvider);
    return Text('Count: $count');
  }
}

언제 쓸까: 스낵바, 다이얼로그, 네비게이션 등 사이드 이펙트를 실행할 때.

listen 활용 예시

// 에러 발생 시 스낵바 표시
ref.listen<AsyncValue<User>>(userProvider, (_, next) {
  next.whenOrNull(
    error: (error, _) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error: $error')),
      );
    },
  );
});

// 로그인 성공 시 화면 이동
ref.listen<AuthState>(authProvider, (_, next) {
  if (next.isLoggedIn) {
    context.go('/home');
  }
});

정리

Widget build(BuildContext context, WidgetRef ref) {
  // UI에 표시 → watch
  final user = ref.watch(userProvider);

  // 사이드 이펙트 → listen
  ref.listen(errorProvider, (_, error) {
    showSnackBar(error);
  });

  return Column(
    children: [
      Text(user.name),
      ElevatedButton(
        onPressed: () {
          // 이벤트 핸들러 → read
          ref.read(userProvider.notifier).logout();
        },
        child: Text('Logout'),
      ),
    ],
  );
}

관련 문서