조용한 담장

Flutter : State management (Provider) 본문

Flutter

Flutter : State management (Provider)

iosroid 2019. 10. 11. 18:17

Flutter : State management (Provider)

앱의 화면 또는 위젯 간에 정보 공유를 위한 방법에 대한 설명을 살펴보자.

Ephemeral state and App state


Ephemeral state (sometimes called UI state or local state) is the state you can neatly contain in a single widget.


state 를 두 종류로 나누어 설명한다.

Ephemeral state 는 한 위젯안에서만 사용하는 state 로써, 앱으로 생각하면 한 화면 안에서만 영향을 주는 변수들을 예로 생각해 볼 수 있다.

데이터를 로딩하면서 프로그레스 바를 그리는 화면이 있다면 그 이후의 앱 페이지 (혹은 위젯) 에게는 로딩 상태에 대한 정보가 의미가 없는 것이다. 다음에 사용되는 위젯은 로딩값이 100 이거나 0 이거나 상관이 없다.

하지만 실제 앱이라면 로딩값이 100 이 아니라면 문제가 발생한 상태일 것이다.

로딩 화면 이후에 동작되는 위젯들에게는 로딩 값이 100이 아니라면 예외처리를 해야할 것이므로 이 값을 알수 있어야 하며 이런 값들은 App state 가 된다.

App state 는 앱 내에서 모든 위젯들이 공유할 수 있는 정보이다.


State that is not ephemeral, that you want to share across many parts of your app, and that you want to keep between user sessions, is what we call application state (sometimes also called shared state).


앱 내에서 어느 값이 두개의 state 중 어디에 속할지는 명확하게 구분되어 있지 않다. 개발중에 ephemeral state 였던 것이 앱이 복잡해지면서 app state 로 변경될 수도 있는 것이다.

아래 그림과 같은 방식을 생각하며 의미를 정리해보면 되겠다.

ephemeral-vs-app-state


In summary, there are two conceptual types of state in any Flutter app. Ephemeral state can be implemented using State and setState(), and is often local to a single widget. The rest is your app state. Both types have their place in any Flutter app, and the split between the two depends on your own preference and the complexity of the app.


Provider

App state management 관련 방법들은 여러가지가 있을 수 있지만 특별히 어렵고 복잡한 방법을 일부러 사용하고 싶지 않다면 provider package 를 사용하는 것을 추천하고 있다.

Provider

Provider({Key key, @required ValueBuilder builder, Disposer dispose, Widget child })


A Provider that manages the lifecycle of the value it provides by delegating to a pair of ValueBuilder and Disposer.


Provider package 의 근간이 되는 provider class 이다.

앱 내에서 공유되는 정보, 값을 관리하는 역할을 한다. 값의 생성과 삭제에 관여한다.

Provider<MyComplexClass>(
  builder: (context) => MyComplexClass(),
  dispose: (context, value) => value.dispose()
  child: SomeWidget(),
)

builder 에 지정된 함수가 호출되어 내부적으로 공유 값을 생성하게 되고 이 값은 child 에 지정된 위젯 과 그 자식 위젯들이 공유하게 된다.

Provider 가 삭제될 때 dispose 에 지정된 함수가 호출되어 값을 삭제하게 된다.

Provider.value

Provider.value({Key key, @required T value, UpdateShouldNotify updateShouldNotify, Widget child })


Allows to specify parameters to Provider.


Provider 의 값을 직접 지정하며 이 값은 child 의 모든 위젯이 공유하게 된다.

Provider<String>.value(
  value: 'Hello World',
  child: MaterialApp(
    home: Home(),
  )
)

값의 삭제는 자동으로 이루어 진다.

Provider.of

Provider.of() method


Obtains the nearest Provider up its widget tree and returns its value.


Provider 의 값을 읽는 간단한 static method 이다.

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(
      /// Don't forget to pass the type of the object you want to obtain to `Provider.of`!
      Provider.of<String>(context)
    );
  }
}

BuildContext 가 접근하기 어려운 상황에서는 사용하기 불편하고 제약이 많이 생긴다.

또한 BuildContext 상에서 연관된 다른 위젯이 모두 업데이트 되므로 로드가 많이 걸릴 수 있다.

MultiProvider

MultiProvider({Key key, @required List providers, Widget child })


A provider that merges multiple providers into a single linear widget tree. It is used to improve readability and reduce boilerplate code of having to nest multiple layers of providers.


여러개의 provider 를 사용할 때 코드를 보기좋게 하기 위해 사용 할 수 있다.

// nested Providers
Provider<Foo>.value(
  value: foo,
  child: Provider<Bar>.value(
    value: bar,
    child: Provider<Baz>.value(
      value: baz,
      child: someWidget,
    )
  )
)
// MultiProvider
MultiProvider(
  providers: [
    Provider<Foo>.value(value: foo),
    Provider<Bar>.value(value: bar),
    Provider<Baz>.value(value: baz),
  ],
  child: someWidget,
)

ChangeNotifier

ChangeNotifier()


A class that can be extended or mixed in that provides a change notification API using VoidCallback for notifications.


ChangeNotifier 는 Flutter SDK 의 기본 class 이다. Listener 에게 notification 을 전달할 수 있다.

FloatingActionButton 을 누르면 counter.increase() 를 통해 카운터 값을 증가 시키고, listener 에게 notification 을 보내 setState() 가 호출되어 UI를 다시 그리게 된다.

ChangeNotifierProvider

ChangeNotifierProvider({Key key, @required ValueBuilder builder, Widget child })


Creates a ChangeNotifier using builder and automatically dispose it when ChangeNotifierProvider is removed from the widget tree.


provider package 의 class 이다.

ChangeNotifier 를 child 위젯들에게 제공하여 state 를 공유할 수 있게 해줌으로써 state 가 변경되어 notification 이 발생하면 관련 위젯들이 업데이트 될 수 있도록 해준다.

아래의 코드처럼 앱의 최상위 위젯을 ChangeNotifierProvider 의 child 로 지정하면 앱의 하위 모든 위젯들이 ChangeNotifier 를 접근 할 수 있게되어 App state 가 공유될 수 있다.

Counter ChangeNotifier 인스턴스가 생성되고 MyApp 의 모든 위젯에게 제공된다.

이 ChangeNotifier 인스턴스를 접근하는 방법에는 Provider.of, Consumer, Selector 가 있다.

Consumer

Consumer({Key key, @required Widget builder(BuildContext context, T value, Widget child), Widget child })


Obtain Provider from its ancestors and pass its value to builder.


builderChangeNotifier가 변경될때 마다 호출되는 함수이다. 즉, notifyListeners() 가 실행될 때 연관된 모든 Consumer 의 builder method 가 호출된다.

Consumer 는 내부적으로 Provider.of 를 사용하여 Counter 를 찾아 builder 에 넘겨준다.

Consumer 는 BuildContext 에 접근하기 힘든 경우에 사용하기 위한 것으로 Builder 를 사용하게 되는 것과 동일하며 어디든 위젯이 사용되는 자리에 쉽게 적용할 수 있는 장점이 있다.

child 를 통해 특정 위젯만 업데이트 되도록 할 수 있다.

'Fake:0' Text 는 consumer 에 의해 처음 빌드 되고 나서 builder 가 호출될때마다 기존의 것이 전달만 되어 업데이트 되지 않는다. Provider.of() 의 listen: false 가 Provider 위젯들의 업데이트를 막는다. 기본값인 true 이면 Provider 의 영향을 받는 모든 위젯이 없데이트 된다.

Selector

Selector({Key key, @required ValueWidgetBuilder<S> builder, @required S selector(BuildContext, A), Widget child })


An equivalent to Consumer that can filter updates by selecting a limited amount of values and prevent rebuild if they don't change.


Provider 의 변화가 상관이 없는 경우 무시하도록 할 수 있다.

Provider.of 를 사용해 값을 얻어 selector 에게 전달하면, selector 의 콜백 함수에서 특정 조건에 맞는 경우에만 builder 가 동작하도록 한다.

Selector<List, int>(
  selector: (context, list) => list.length,
  builder: (context, length, child) {
    return Text('$length');
  }
);

list.length 의 값이 변할때 만 Text() 를 빌드한다.

Different types of providers

여러개의 Provider 사용시에는 타입이 동일하면 안된다.

공식 문서의 예제처럼 이 문제를 처리할 수 있다.

// tree 에서 가장 가까운 것의 값을 얻게 되어 값을 신뢰할 수 없다.
Provider<String>(
  builder: (_) => 'England',
  child: Provider<Sring>(
    builder: (_) => 'London',
    child: ...,
  ),
),
// 타입이 다르므로 얻게 될 값이 명확하다.
Provider<Country>(
  builder: (_) => Country('England'),
  child: Provider<City>(
    builder: (_) => City('London'),
    child: ...,
  ),
),

Provider examples

provider_counter

provider_shopper

Reference

State management

provider package

provider package document

'Flutter' 카테고리의 다른 글

Flutter : Navigator  (0) 2019.10.17
Flutter : Text Widgets  (0) 2019.10.14
Flutter : declarative UI  (0) 2019.10.08
flutter : CustomPaint  (0) 2019.10.07
Flutter : iOS Application Bundle  (0) 2019.09.11
Comments