Provider

luneah·2022년 8월 15일
0

Flutter

목록 보기
22/29
post-thumbnail

상태관리 (State Management)

상태 = 데이터 = 변수
변수를 수정하면 알아서 UI도 바뀌게 하자!

상태의 종류

  1. 임시 상태
    • pageView의 index
    • BottomNavigationView의 index
    • 애니메이션 상태
  2. 그 외, 어플리케이션 상태
    • Preference
    • 로그인 정보
    • 쇼핑몰의 카트
    • 메일앱의 읽은 메일/안 읽은 메일
    • 소셜앱의 알림

정해진 룰은 없다. 대부분의 경우 setState()만으로 충분하다. 하지만 비동기 처리(네트워크 통신 등의 오래 걸리는 처리)가 많으면 다른 상태관리 도구를 선택하는 것이 좋다.

상태관리를 편하게 하기 위한 라이브러리들 : Bloc, Provider, RiverPod, GetX 등

Provider

  • InheritedWidget과 가장 흡사하다.
  • 제약이 많다. = 에러 내기가 어렵다.
  • 구글에서 공식적으로 밀고 있다.
  • 다른 라이브러리는 제대로 알고 쓰지 않으면 코드가 망가진다.

ChangeNotifierProvider

Provider 기본 사용 방법

  1. 모델 작성(상태 + 로직)
// Logic
import 'package:flutter/material.dart';

class CounterModel extends ChangeNotifier {
  int count = 0;

  void increase() {
    count++;
    notifyListeners();
  }
}
  1. Provider 의존성 추가
    flutter pub add provider

  2. 제공할 위젯 트리의 최상단에 ChangeNotifierProvider 위젯 배치

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CounterModel(),
      child: const MyApp(),
    ),
  );
}
  1. 모델을 제공할 가장 안쪽에 Consumer 배치
body: Center(
  child: Consumer<CounterModel>(
    builder: (context, model, child) {
      return Text(
        '${model.count}',
        style: const TextStyle(fontSize: 40),
      );
    },
  ),
),
  1. 단발성 이벤트 처리
floatingActionButton: FloatingActionButton(
  child: const Icon(Icons.add),
  onPressed: () {
    context.read<CounterModel>().increase();
  },
),

Provider.of<CounterModel>(context, listen: false).increase(); 와 동일한 코드이다.(예전 방식)

  • Consumer 대신 다음과 같이 사용할 수 있다.
    
    Widget build(BuildContext context) {
    	final model = context.watch<CounterModel>();
    }
    body: Center(
    child: Text(
          '${model.count}',
          style: const TextStyle(fontSize: 40),
        );
    ),

Stream + Provider

  1. 제공할 객체 준비

    class HomeViewModel {
     final counter = Counter();
    
     final _streamController = StreamController<int>();
    
     int get count => counter.count;
    
     Stream<int> get countStream => _streamController.stream;
    
     void increase() {
       counter.increase();
       _streamController.add(counter.count);
     }
    }
  2. 제공할 위젯 트리의 최상단에 Provider 위젯 작성
    Stream을 안 쓰는 경우 Provider보다 ChangeNotifierProvider를 주로 사용한다. 상태 변경을 자동으로 모니터링 한다,

    
    Widget build(BuildContext context) {
     return MaterialApp(
       title: 'Flutter Demo',
       theme: ThemeData(
         primarySwatch: Colors.blue,
       ),
       home: Provider(
         create: (_) => HomeViewModel(),
         child: const MyHomePage(),
       ),
     );
    }
  3. 사용할 때는 Provider.of(context)로 객체를 얻는다.
    확장함수 context.watch()와 동일

    
    Widget build(BuildContext context) {
    	final viewModel = Provider.of<HomeViewModel>(context);
  4. 변경사항이 있을 객체에 ChangeNotifier 구현

 - class HomeViewModel {
 + class HomeViewModel with ChangeNotifier {
	final counter = Counter();
  1. Stream은 제거하고 변경사항이 있을 때 notifyListeners() 호출
void increase() {
	counter.increase();
   - _streamController.add(counter.count);
   + notifyListeners();
  1. Provider 대신 ChangeNotifierProvider로 변경
- home: Provider(
+ home: ChangeNotifierProvider(
	create: (_) => HomeViewModel(),
    child: const MyHomePage(),
),
  1. StreamBuilder 제거
- StreamBuilder<int>(
	stream: viewModel.countStream,
    builder: (context, snapshot) {
    	return Text(
        	'${viewModel.count}',
            style: Theme.of(context).textTheme.headline4,
        );
    }),
+ Text(
	'${viewModel.count}',
    style: Theme.of(context).textTheme.headline4,
),

ChangeNotifierProvider + Consumer

StreamBuilder와 유사한 느낌, Consumer를 통해서 ViewModel을 직접 접근


Widget build(BuildContext context) {
  - final viewModel = Provider.of<HomeViewModel>(context);
  return Scaffold(
    appBar: AppBar(
      title: const Text('title'),
    ),
  ...
        const Text(
          'You have pushed the button this many times: ',
        ),
        - Text(
	          '${viewModel.count}',
            style: Theme.of(context).textTheme.headline4,
          ),
        + Consumer<HomeViewModel>(
            builder: (_, viewModel, child) {
              return Text(
	              '${viewModel.count}',
                style: Theme.of(context).textTheme.headline4,
              );
            },
        ),
}

Provider VS GetX

Provider
Provider는 consumer를 이용해 상태값을 가져오고 provider와 context를 이용해 controller의 비즈니스 로직을 불러온다. provider의 경우, stateful 위젯으로 변경하거나 context를 넘겨줘야한다.

  • controller

    class CountControllerWithProvider extends ChangeNotifier {
     int count = 0;
     void increase() {
       count++;
       notifyListeners();
     }
    }
  • ui

    class WithProvider extends StatelessWidget {
     const WithProvider({Key? key}) : super(key: key);
    
     
     Widget build(BuildContext context) {
       return Center(
         child: Column(
           children: [
             Text(
               "Provider",
               style: TextStyle(fontSize: 50),
             ),
             Consumer<CountControllerWithProvider>(
               builder: (_, snapshot, child) {
                 return Text("${snapshot.count}", style: TextStyle(fontSize: 50));
               },
             ),
             RaisedButton(
               onPressed: () {
                 // listen: false를 줘서 consumer 부분만 rebuild
                 Provider.of<CountControllerWithProvider>(context, listen: false)
                     .increase();
               },
               child: Text(
                 "+",
                 style: TextStyle(fontSize: 50),
               ),
             ),
           ],
         ),
       );
     }
    }

GetX
GetX는 GetBuilder를 통해 상태값을 가져오고 Get.find를 통해 controller의 비즈니스 로직을 가져온다. context를 사용하지 않는 GetX 방식은 위젯으로 분리하여 사용 가능하다. 부모에서 controller를 선언해줄 필요없이 ui 부분에서 바로 사용 가능하다. 또한 클래스가 생성될때 바로 사용가능하다.

  • controller

    class CountControllerWithGetX extends GetxController {
     int count = 0;
     void increase() {
       count++;
       update();
     }
    }
  • ui

    class WithGetX extends StatelessWidget {
     const WithGetX({Key? key}) : super(key: key);
    
     
     Widget build(BuildContext context) {
       return Center(
         child: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: [
             Text(
               "GetX",
               style: TextStyle(fontSize: 50),
             ),
             GetBuilder<CountControllerWithGetX>(builder: (controller) {
               return Text(
                 "${controller.count}",
                 style: TextStyle(fontSize: 50),
               );
             }),
             RaisedButton(
               onPressed: () {
                 Get.find<CountControllerWithGetX>().increase();
               },
               child: Text(
                 "+",
                 style: TextStyle(fontSize: 50),
               ),
             ),
           ],
         ),
       );
     }
    }
profile
하늘이의 개발 일기

0개의 댓글