GoToLearn회고_FlutterCodeLab 6~7

guddls ju·2024년 6월 20일
0

6~7번째 스터디 주제는 flutter의 상태관리 도구 Riverpod이다.
간단한 카운터로 Riverpod에 대해 경험해보았고 조금 더 공부를 하고나서
BMI 계산기를 만들었다.
나는 첫 프로젝트부터 BLoC을 도입해서 Riverpod은 처음 사용해 보았다.

Riverpod 이란?

flutter의 상태관리 패키지로 Provider를 기반으로 만들었으나, Provider의 한계와 단점을 극복하기 위해 디자인 되었다.
여담으로 riverpod의 철자는 provider를 애너그램 하여 만들었고, 동일 인물이 만들었다.
(변태+천재;;)

특징

  • 경량화 - riverpod은 상태관리 패키지 임에도 불구하고 간단하게 사용할 수 있다는 장점이 있다. (근데 점점 복잡해지고 있다고...)
  • 어플리케이션 상태를 쉽게 관리하고 액세스하는 동시에 상태 불변성, 상태 격리, 의존성 주입과 같은 강력한 기능을 제공한다.
  • Notifier와 Provider를 이용하여 상태관리 기능을 제공한다.

사용방법

ProviderScope로 위젯을 감싼다. (하위 위젯 어디서든 접근할 수 있음)

void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

Provider를 정의한다. (간단한 동작을 넣어주고)

class Counter extends StateNotifier<int>{
   Counter(int num) : super(num);

   void increment(){
     state++; 
   }
 }

final counterProvider = StateNotifierProvider<Counter, int>((ref) {
  return Counter(0);
});

Stateless위젯 대신 ConsumerWidget을 사용하고
ref.watch를 통해 변경을 감지하여 notifier를 사용해서 UI를 업데이트 해준다!

class MyApp extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final int value = ref.watch(counterProvider);

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Example')),
        body: Center(
          child: Text(value.toString()),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: (){
            ref.read(counterProvider.notifier).increment(); 
          },
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

상태관리도구 치고는 상당히 간단한 사용법이다.
자 이제 진짜를 알아볼까?

Provider에 대하여

Riverpod은 provider를 사용해서 상태관리를 하는데 많이 사용하는 provider들을 알아보자.

  • Provider
    - 가장 기본이 되는 Provider로 단순히 값을 읽을 수만 있다.

  • StateNotifierProvider
    - 이름에서 알 수 있듯 상태 변화를 관찰하고 변경된 상태를 알려주는 Provider이다. 사용자와 상호작용이나 계속적으로 변화하는 상태를 관리할 때 사용한다. (가장 많이 쓸듯)

  • StateProvider
    - StateNotifierProvider 얘보다 단순한 상태 관리할 때 쓴다는데 쓰다보면 걍 위에꺼만 쓰게되지 않을까 싶다.

  • FutureProvider
    - 일반적인 Provider의 역할이지만 비동기 처리가 가능해서 네트워크 요청, 파일 입출력, 데이터베이스 입출력 등에 사용한다.

  • StreamProvider
    - FutureProvider랑 비슷하지만 Stream처리에 유용하다. (채팅같은거 할때 쓰는듯?)

  • ChangeNotifierProvider
    - StateNotifierProvider와 같은 기능이지만 공식문서에서는 mutable한 상태가 아니라면 Rivderpod에서는 StateNotifierProvider사용을 권장하고 있다.

ConsumerWidget & ref

이제 다음은 이 친구들을 알아보자

  • ConsumerWidget
    - StateLess위젯을 Riverpod에서 사용하기위해 변형한 형태로 ConsumerWidget을 상속하면 build함수의 파라미터로 받아 WidgetRef를 사용할 수 있다.
class Example extends ConsumerWidget {
  const SearchBar({Key? key}): super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    String keyword = ref.watch(keywordProvider);
    ...
  }
}
  • ConsumerStateFulWidget + ConsumerState
    - 한단계 더 나아가면 StateFul위젯처럼 사용할 수 있는 ConsumerStateFulWidget에서 ConsumerState는 ref가 속성으로 정의되어 있어 WidgetRef를 build 외의 다른 함수에서도 사용할 수 있다.
class Example extends ConsumerStatefulWidget {
  const SearchBar({Key? key}): super(key: key);

  
  ConsumerState<ConsumerStatefulWidget> createState() => _SearchBar();
}

class Example extends ConsumerState<Example> {
  
  Widget build(BuildContext context) {
    String keyword = ref.watch(keywordProvider);
    ...
  }
}

ref - Provider간의 상호작용을 위한 객체로, 위젯과 Provider 사이의 브리지 역할을 하여 Provider와 상호 작용하고 변경을 관찰하여 상태를 관리하는 다양한 작업을 수행한다. 사용방법을 알아보면~

  • ref.watch
    - Provider를 구독해 변화를 감지 할 수 있고, 값이 변경되면 위젯을 다시 빌드하거나 구독 하고 있는 위치에 상태를 전달한다. 상태 변화를 화면에 즉각 반영해야할 때 사용.

  • ref.read
    - Provider의 상태를 가져오고 값을 변경할 수 있다. 일반적으로 사용자와 상호작용으로 발생 가능한 트리거 함수에서 사용.

  • ref.listen
    - watch와 동일하게 변화를 모니터링 하지만 위젯을 rebuild 하거나 상태를 전달하지 않고 값이 변경될 때 정의한 함수를 실행.

이게 보다보니까 BLoC이랑도 비슷하다. BlocConsumer위젯, BlocListner위젯 거기서 사용하는 listner 등 동작도 비슷하고 이름도 비슷하다. Riverpod을 먼저 써봤으면 어땠을까... 하는 생각이든다ㅎ 삽질 좀 덜했으려나ㅠ
flutter에서 상태관리 패키지를 처음 사용해보신다면 저는 Riverpod을 추천드리겠습니다!


BMI 계산기 만들기

카운터와 별로 다를게 없다.

계산하는 Provider를 만들어준다

class Calculator extends StateNotifier<double> {
  Calculator() : super(0.0);

  void calculateBMI(double kg, double m) {
    state = double.parse((kg / (m * m) * 10000).toStringAsFixed(1));
  }
}

final calculatorProvider = StateNotifierProvider<Calculator, double>((ref) {
  return Calculator();
});

키와 몸무게를 입력받아준다.

  SizedBox(
          width: 250,
          child: TextField(
            controller: weightController,
            keyboardType: TextInputType.number,
            decoration: const InputDecoration(
              border: OutlineInputBorder(),
              hintText: 'Enter your weight in kg',
              labelText: 'kg',
            ),
          ),
        ),
        const SizedBox(height: 20),
        SizedBox(
          width: 250,
          child: TextField(
            controller: heightController,
            keyboardType: TextInputType.number,
            decoration: const InputDecoration(
              border: OutlineInputBorder(),
              hintText: 'Enter your height in m',
              labelText: 'm',
            ),
          ),
        ),

계산해서 화면에 딱~!

final double bmi = ref.watch(calculatorProvider);

...

    ElevatedButton(
          onPressed: () {
            final weight = double.tryParse(weightController.text);
            final height = double.tryParse(heightController.text);
            if (weight != null && height != null) {
              ref
                  .read(calculatorProvider.notifier)
                  .calculateBMI(weight, height);
            }
          },
          child: const Text('Calculate BMI'),
        ),
        const SizedBox(height: 50),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Your BMI ', style: const TextStyle(fontSize: 25)),
            Text(bmi.toString(),
                style:
                    const TextStyle(fontSize: 28, fontWeight: FontWeight.bold)),
          ],
        ),

그러면은 요렇게 딱 나온다~


질문과 회고는 너무 기니까 다음 포스팅에서~

profile
효율에 미친자

0개의 댓글