본문 바로가기
Flutter

Flutter[플러터] - Riverpod(리버팟) 상태관리 누구나 쉽게! (2)

by s_hoonee 2024. 11. 7.
반응형

 

우선 리버팟을 사용하기 위해 리버팟에 있는 프로바이더들의 종류와 각 프로바이더의 역할을 이해해야한다. 

대표적으로 3가지만 예시를 들겠다 (이 3개로 웬만한 건 모두 해결 가능하긴 하다.. 비동기도 커스텀이 가능하기에)

  1. Provider
  2. StateProvider
  3. NotifierProvider

다음 3가지 기준으로 무엇을 선언할지 판단

  1. 기본 상태 관리
    • 상태 변화가 필요한 경우
    • Provider 사용
  2. 단순 상태 관리
    • 상태 변화는 필요하지만 로직이 단순한 경우
    • StateProvider 사용
  3. 복잡한 상태 관리
    • 상태 변화가 필요하고 복잡한 로직이 포함된 경우
    • NotifierProvider 사용

복잡한 로직의 기준

  • 실제 사용하면서 판단하는 것을 권장
  • 예시:
    • 단순: 단일 int 값 관리
    • 복잡: int 값을 다른 상태나 API와 연동하여 다양한 기능 수행

setState 대신 StateProvider 사용하기

  • 기본 Flutter 카운터 앱을 예시로 설명
  • 기존: FloatingButton으로 counter 직접 조작
  • 변경: StateProvider를 사용하여 counter 관리

 

stateProvider 적용 전 코드 

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});


  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

 

stateProvider 적용 후 코드 

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final counterProvider = StateProvider<int>((ref) => 0);

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Consumer(builder: (context, ref, child) {
              final counter = ref.watch(counterProvider);

              return Text(
                '$counter',
                style: Theme.of(context).textTheme.headlineMedium,
              );
            }),
          ],
        ),
      ),
      floatingActionButton: Consumer(builder: (context, ref, child) {
        return FloatingActionButton(
          onPressed: () {
            ref.read(counterProvider.notifier).update((state) => state + 1);
          },
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        );
      }), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

1. _counter 변수를 지우고 클래스 외부에 counterProvider를 만들어주었다. 여기서 0은 초기값이다. 만약 1부터 시작하고 싶으면 1로 쓰면 된다,.

final counterProvider = StateProvider<int>((ref) => 0);

2. 클래스 내부 지역변수에 _counter변수가 없으니 ref라는 WidgetRef 클래스의 인스턴스를 이용하여 내가 만든 프로바이더를 찾을 수 있다. 위젯에서 ref를 쓸며녀 Cousumer위젯으로 덮어주면 된다.

ref.watch 문법으로 counterProvider를 가져와서 counter변수에 저장했다. counterProvider를 선언할 때 int값을 선언했으므로 이 타입은 int 와 같다 

ref.watch / ref.read 두 가지 방식으로 값을 가져올 수 있는데 이건 추 후 설명하겠다.

Consumer(builder: (context, ref, child) {
  final int counter = ref.watch(counterProvider);

  return Text(
    '$counter',
    style: Theme.of(context).textTheme.headlineMedium,
  );
}),

3. conterProvider의 값을 변경해보자

ref에 접근하기 위해 좀 전에랑 똑같이 Consumer 위젯으로 덮어줬다. 

프로바이더의 값을 변경하려면 notifier가 필요하다

ref.read(counterProvider.nonifier)  이렇게 인스턴스를 만들고 . 을 누르면 이 프로바이더가 가지고 있는 메소드 혹은 필드 들이 나온다.

위에서 설명한 stateProvider말고 notifierProvider는 이 메소드들과 필드들을 커스텀하여 개발자 입맛에 맞게 추가할 수 있다. (그래서 복잡한 로직에 사용)

내장되어있는 update 메소드를 이용하여 기존 state값 즉, counter값을 +1 시켜서 저장한다. 이러면 Consumer 위젯 아래에 ref.watch로 counterProvider를 구독하고 있는 모든 필드의 값이 리빌드 된다.

즉 setState없이 자동으로 리빌드 되고 Consumer아래로만 리빌드 되어 위젯의 리빌드를 최소화 할 수 있다. 

또한 다른 외부 위젯에서도 이 값에 접근이 가능하여 static 변수처럼 사용가능하다.

floatingActionButton: Consumer(builder: (context, ref, child) {
  return FloatingActionButton(
    onPressed: () {
      ref.read(counterProvider.notifier).update((state) => state + 1);
    },
    tooltip: 'Increment',
    child: const Icon(Icons.add),
  );
}),