본문 바로가기

study/flutter

Flutter- StateManagement, Provder

공식문서를 보고 나름대로 공부해서 정리한 내용임 front와 flutter 유아수준이니 믿지말것.

https://flutter.dev/docs/development/data-and-backend/state-mgmt

 

※ 수시로 바뀌니 읽어보는게 좋음 

https://pub.dev/packages/provider

 

Flutter를 사용하다보면 screen들간에 state를 공유해야 할 일이 많다.

우리가 평소에 배운 setstate를 활용해서 전체 UI를 갱신하는 방법도 있을 수 있겠지만 너무 비효율적이다.

 

Flutter는 선형형언어이다.(declarative) 현재의 state를 build해서 화면에 보여준다.

첫째 우리는 앱의 모든 state를 관리하진 않는다. 

둘째 우리가 관리하는 state는 크게 Ephemeral state와 App state로 나눌 수 있다.

 

 

Emphemeral state

single widget에 포함되는 state이다.

뭔가 말이 모호한데 PageView에서 현재의 Page나 BottomNavigationBar에서 현재 선택된 탭을 생각할 수 있다.

다른 위젯트리는 이러한 state를 사용할 일이 거의 없다. 따라서 state 관리를 할 필요없이 간단히 Statefulwidget으로 구현할 수 있다.

 

간단한 bottomnavigation bar가 포함된 페이지다. 여기서 _index가 ephemeral state이다.

class MyHomepage extends StatefulWidget {
  @override
  _MyHomepageState createState() => _MyHomepageState();
}

class _MyHomepageState extends State<MyHomepage> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      currentIndex: _index,
      onTap: (newIndex) {
        setState(() {
          _index = newIndex;
        });
      },
      // ... items ...
    );
  }
}

여기서는 setState와 _index의 사용이 매우 자연스럽다. _index는 다른 위젯에서 전혀 필요하지 않으므로 MyHomePage위젯 안에서 변경된다. 

 

 

 

App state

Ephemeral state와 다르게 앱의 다양한 part에서 공유하길 원하는 state이다. 

  • User preferences
  • Login info
  • Notifications in a social networking app
  • The shopping cart in an e-commerce app
  • Read/unread state of articles in a news app

위의 상황일 때는 Appstate가 사용된다.

 

그렇다면 둘을 명확하게 구분할 수 있을까?

 

flutter의 공식문서에는 명확한 방법을 제시하고 있진않다.

간단한 앱인 경우는 State와 setState를 활용해 state를 관리할 수 있지만, 앱의 규모가 커지거나 다양한 기능이 적용될 때는 emphemeral state가 app state로 변경 될 수 도 있다.

따라서 요약하자면 Ephemeral state는 State와 setState를 사용해서 실행되는 싱글위젯에서 돌아가는 state이고 나머지는 App state라고 한다. 결국 Dan Abramov의 아래 말대로 어색하지 않은대로 사용하자.

 

“The rule of thumb is: Do whatever is less awkward.”

 

 

 

Example

state management에는 provider을 사용한다.

다른 관리법을 몰라서 뭔지 모르겠지만 암튼 처음하면 provider을 이용하는게 좋다고함 ㅇㅇ

 

이러한 구조의 앱을 만든다고 생각할 때 고민할점

This takes us to our first question: where should we put the current state of the cart?

Cart의 state는 MyListItem에서 필요할 수도 있어서 이를 고려한 설계가 필요함.

 

플러터에서는 state를 사용하는 위젯을 위에서 state를 유지시키는게 일반적임.

위에서 유지하고 바뀔때마다 rebuild하는게 해야한다. 바뀌는부분만 바꾸는건 쉽지않음.

결국 contents가 바뀔따마다 새로운 위젯을 만들어내야한다.

 

 

따라서 cart의 state를 Myapp에 둔다음 MyCart을 rebuild하는 방식으로 실행된다.

Widget은 immutable하다. 바뀌는것이 아닌 state에 따라 replace되는 것이다.

 

 

그렇다면 MyListItem에서 클릭되는 아이템을 cart에 어떻게 담을 수 있을까

아래와 같이 간단히 콜백을 활용해서 담을 수 있다.

@override
Widget build(BuildContext context) {
  return SomeWidget(
    // Construct the widget, passing it a reference to the method above.
    MyListItem(myTapCallback),
  );
}

void myTapCallback(Item item) {
  print('user tapped on $item');
}

작동하기는 하지만 이렇게 콜백을 쌓다보면 하나 변경할려면 수많은 콜백을 거쳐야한다.

따라서 InheritedWidget, InheritedNotifier, InheritedModel 와 같은 다양한 WIdget들을 사용해 descendatnts에 data를 제공하는 방법들을 사용해야한다.

여기서는 provider라는 pakage를 사용한다. 위의 방법보다 간단하다는데 위에거를 안써봐서 차이점은 모름

 

provider을 이해하려면 먼저 3가지 개념을 알아야한다.

  • ChangeNotifier
  • ChangeNotifierProvider
  • Consumer

ChangeNotifier

listeners에게 변경된걸 갈켜주는 간단한 클래스이다.

provider에서 ChangeNotifier은 state를 압축하는 하나의 방법임. 복잡할 경우 ChangeNotifiers를 사용한다.

class CartModel extends ChangeNotifier {
  /// Internal, private state of the cart.
  final List<Item> _items = [];

  /// An unmodifiable view of the items in the cart.
  UnmodifiableListView<Item> get items => UnmodifiableListView(_items);

  /// The current total price of all items (assuming all items cost $42).
  int get totalPrice => _items.length * 42;

  /// Adds [item] to cart. This is the only way to modify the cart from outside.
  void add(Item item) {
    _items.add(item);
    // This call tells the widgets that are listening to this model to rebuild.
    notifyListeners();
  }
}

notifyListeners()을 통해 변경됐음을 알려준다.

 

 

ChangeNotifierProvider

ChangeNotifierProvider은 그들의 자손한테 ChangeNotifier의 인스턴스를 제공하는 위젯이다.

위의 모델인 경우 MyCart와 MyCatalog 위에서 존재한다.

하지만 ChangeNotifierProvider을 필요이상으로 높게 배치하면 안된다.

 

void main() {
  runApp(
    ChangeNotifierProvider(
      builder: (context) => CartModel(),
      child: MyApp(),
    ),
  );
}

provide할 class가 많은 경우에는 MultiProvider사용

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(builder: (context) => CartModel()),
        Provider(builder: (context) => SomeOtherClass()),
      ],
      child: MyApp(),
    ),
  );
}

 

 

 

 

Consumer

위에서 ChangeNotifierProvider을 통해 만든 CarModel을 Consumer을 통해 사용할 수 있다.

return Consumer<CartModel>(
  builder: (context, cart, child) {
    return Text("Total price: ${cart.totalPrice}");
  },
);

Consumer하고 <> gerneric에 꼭 쓸거 명시해줘야한다.

Consumer에서 꼭 필요한 argument는 builder이다.

ChangeNotifier가 변경됐을때 (notifyListeners()가 실행됐을때) builder가 실행된다.

각각의 인자를 보자

context : 걍 필요한거

cart : ChangeNotifier의 새로운 인스턴스.

child : 최적화를 위한 것, 하위 위젯들을 전부 build할 필요없을때 예외로 뺄 수 있다.

return Consumer<CartModel>(
  builder: (context, cart, child) => Stack(
        children: [
          // Use SomeExpensiveWidget here, without rebuilding every time.
          child,
          Text("Total price: ${cart.totalPrice}"),
        ],
      ),
  // Build the expensive widget here.
  child: SomeExpensiveWidget(),
);

 

 

// DO THIS
return HumongousWidget(
  // ...
  child: AnotherMonstrousWidget(
    // ...
    child: Consumer<CartModel>(
      builder: (context, cart, child) {
        return Text('Total price: ${cart.totalPrice}');
      },
    ),
  ),
);

consumer로 묶을때는 바뀌는 작은부분만 묶어라.

 

Provider.of

Provider.of<CartModel>(context, listen: false).removeAll();

clear 버튼 같이 UI변경없이 data를 변경할때 전체를 Consumer로 묶는건 비효율적이다 따라서 위와같은 방법으로 접근하면 좋음.

    var cart = Provider.of<CartModel>(context);

 이렇게도 접근가능

 

  • context.watch<T>(), which makes the widget listen to changes on T
  • context.read<T>(), which returns T without listening to it

provider.of<T>가 watch read로 변경된듯.

context.read<CartModel>().removeAll();  //https://pub.dev/packages/provider 참조

 

 

 

ChangeNotifierProxyProvider

https://pub.dev/documentation/provider/latest/provider/ChangeNotifierProxyProvider-class.html

 

ChangeNotifierProxyProvider class - provider library - Dart API

ChangeNotifierProxyProvider class A ChangeNotifierProvider that builds and synchronizes a ChangeNotifier with external values. To understand better this variation of ChangeNotifierProvider, we can look into the following code using the original provider: C

pub.dev

 

 

 

 

 

 

++

provider 구조 쉽게 설명해놈 

www.youtube.com/watch?v=NeAMD0lQ5jw

 

대강 써보니까 

변할때마다 rebuild해야하면 consumer 변화만 알려주면되면 watch 그냥 값만 읽어오면 read를 사용하는것 같다.

근데 watch는 build method 안에서만 read는 initsate에서 사용해야하는것 같은데.

기능적으로보면 당연한 소리지만 context.read를 사용하기위해 무조건 stateful로 만들어야하는건가??

 

 

 

 

 

'study > flutter' 카테고리의 다른 글

Flutter -async  (0) 2020.07.29