개발/Android

[Flutter] Riverpod 상태관리 - ConsumerWidget 가이드

y_lime 2025. 4. 2. 11:05

🔍 ConsumerWidget이란?

ConsumerWidget은 flutter_riverpod에서 제공하는 위젯으로, Consumer와 동일한 역할을 하지만 더 간결한 코드 작성을 가능하게 한다.

Consumer vs ConsumerWidget

  Consumer ConsumerWidget
사용 방식 Consumer(builder: (context, ref, child) {...}) build 메서드에서 ref.watch 사용 가능
코드 구조 StatelessWidget + Consumer 조합 필요 StatelessWidget을 대체하여 더 간결함
위젯 성능 특정 부분만 리빌드 가능 특정 부분만 리빌드 가능
추천 사용처 위젯의 특정 부분만 ref.watch가 필요할 때 전체 위젯에서 ref.watch를 사용할 때

 


1. Consumer vs ConsumerWidget 사용 추천

✅ Consumer를 사용해야 할 때

  • 일부 위젯만 리빌드하고 싶을 때 (예: 특정 Text 위젯만 변경)
  • 부모 위젯이 StatelessWidget이나 StatefulWidget일 때

✅ ConsumerWidget을 사용해야 할 때

  • 위젯 전체에서 ref.watch가 필요할 경우
  • StatelessWidget을 대체하고 싶은 경우

예제) Consumer를 활용하여 Text만 리빌드

(전체 코드 예시는 아래에서 더 설명할 예정)

Consumer(
  builder: (context, ref, child) {
    final count = ref.watch(counterProvider);
    return Text('Count: $count');
  },
)

 

2. 일반 StatelessWidget에서는 ref를 직접 사용할 수 없다!

만약 일반 StatelessWidget에서 ref.watch()를 사용하면 오류가 발생한다.

❌ 일반 StatelessWidget에서 ref를 사용한 경우

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final count = ref.watch(counterProvider);  // ❌ 오류 발생

    return Scaffold(
      appBar: AppBar(title: const Text('Riverpod Counter')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Count: $count',
              style: const TextStyle(fontSize: 24),
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IconButton(
                  onPressed: () => ref.read(counterProvider.notifier).decrement(),// ❌ 오류 발생

                  icon: const Icon(Icons.remove),
                ),
                IconButton(
                  onPressed: () => ref.read(counterProvider.notifier).increment(),// ❌ 오류 발생

                  icon: const Icon(Icons.add),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

이 코드에서는 ref.watch(counterProvider)를 사용할 수 없기 때문에 오류가 발생한다.

 

  • StatelessWidget은 ref.watch()를 직접 사용할 수 없다.
  • ref는 ConsumerWidget이나 ConsumerStatefulWidget 내부에서만 사용 가능.

 

해결 방법은?

  • 방법1 ) ConsumerWidget을 사용한다.
  • 방법2 ) Consumer 위젯을 build 내부에서 감싸서 사용한다.

 방법 1 (ConsumerWidget)

class CounterScreen2 extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider); // count 변경 시 전체 빌드됨

    print("CounterScreen2 전체 빌드됨!");

    return Scaffold(
      appBar: AppBar(title: const Text('Riverpod Counter')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Count: $count', // count 변경 시 전체 빌드됨
              style: const TextStyle(fontSize: 24),
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IconButton(
                  onPressed: () => ref.read(counterProvider.notifier).decrement(),
                  icon: const Icon(Icons.remove),
                ),
                IconButton(
                  onPressed: () => ref.read(counterProvider.notifier).increment(),
                  icon: const Icon(Icons.add),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

 

 방법 2 (Consumer)

class CounterScreen3 extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    print("CounterScreen3 빌드됨!"); //빌드 할 때 한 번만 빌드 됨

    return Scaffold(
      appBar: AppBar(title: const Text('Riverpod Counter')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Consumer(
              builder: (context, ref, child) {
                final count = ref.watch(counterProvider);
                print("count 텍스트만 빌드됨!");
                return Text(
                  'Count: $count',
                  style: const TextStyle(fontSize: 24),
                );
              },
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IconButton(
                  onPressed: () => ref.read(counterProvider.notifier).decrement(),
                  icon: const Icon(Icons.remove),
                ),
                IconButton(
                  onPressed: () => ref.read(counterProvider.notifier).increment(),
                  icon: const Icon(Icons.add),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

3. ConsumerWidget vs. ConsumerStatefulWidget, 언제 뭘 써야 할까?

그렇다면 무조건 ConsumerWidget을 써야 할까?
아래 기준을 참고하면 된다.

setState()를 사용하지 않고, Riverpod 상태만 구독할 경우 ✅ ConsumerWidget
setState()가 필요하고, 내부적으로 상태를 변경해야 하는 경우 ✅ ConsumerStatefulWidget

 

정리하면?
- 별도의 상태 관리가 필요 없고, Riverpod 상태만 구독하면 ConsumerWidget을 쓰는 게 더 좋다.
- 내부적으로 setState()를 써야 한다면 ConsumerStatefulWidget을 사용해야 한다.

 

ConsumerStatefulWidget 에 대해서는 다음 글에서 더 자세하게 설명되어 있다.

https://jaslime.tistory.com/50

결론

  • ConsumerWidgetStatelessWidget을 대체하며 ref.watch를 간편하게 사용할 수 있음.
  • Consumer는 특정 UI 부분만 리빌드할 때 유용함.
  • 프로젝트에 따라 적절한 방식을 선택하여 사용하면 더 깔끔한 코드를 유지할 수 있음!