Pragmatic State Management in Flutter (Google I/O'19)のメモ

Posted by kwmt27 on Sat, May 18, 2019

問題点

ネットワークから、他のwidgetからシステムコールなどから、widgetの状態変更したい場合は多くあります。

たとえば3つのWidgetがあったとき、MySliderからMyChartの状態を変更する場合、下図のようにしたくなるかもしれませんが、これにはいくつか問題があります。

1.widgetが強参照している(strongly coupling widget)

つまりMySliderはMyChartのことを知っている→これは拡張性がなくなります。not very scalable

2.グローバルの状態を持っている

多くのチャートがある場合、同期されてしまいます。

3.外のwidgetからsetStateを呼んでいること これは最悪

  • どこから状態が変わったのか把握できない
  • クラッシュする可能性がある

下図は、UI is a functional of stateです。UI=レイアウトは、現在のアプリケーションの状態がビルドメソッドを介して決まります。

つまりは、UIは他のUIから変更されません。 findViewByIdのようなものはありません。

対策

これを解決するためには、両方のwidgetにアプローチ、アクセスできる何かが必要です。 これを今回はMyScheduleと呼びましょう。

これはMyHomePageにアタッチされます。なぜなら両方のwidgetをしっているから。

MySliderが直接MyChartを触る代わりにMyScheduleを通してMyChartに伝えます。

MySlider -> MyScheduler -> MyChartに通知notify MyChart -> MySchedular -> MySliderに変更したよ結果を通知

コードはどのようになるか?

そのためにGoogleの状態管理アプローチの歴史を見る

  • Scoped model

widgetのトップにモデルがあり子孫があるとき、 モデルが変更されたら、それらの子孫は通知される、。

これは使われているが、いくつかの機能が不足している

  • BLoC 複雑なアプリが複数あるAdwordsのためにつくられた。 Rxとstremaベース これは複雑すぎるという意見を聞いた

あるいは、Flutterをやるにはnewアプローチをしないと

  • package:provider コミュニティベース これをGoogle内部で使っている Scopedモデルに近い

Providerを使うには

まずMyScheduleを定義しましょう。

import 'package:flutter/widgets.dart';

class MySchedule with ChangeNotifier {
  double _stateManagementTime = 0.5;

  double get stateManagementTime => _stateManagementTime;

  set stateManagementTime(double newValue) {
    _stateManagementTime = newValue;
    notifyListeners();
  }
}

このMyScheduleをMyHomePageにアタッチするには、 トップにchangenotifierproviderを次のようにラップします

ChangeNotifierProvider(
      builder: (context) => MySchedule(),
      child: Scaffold(

これでツリーにstateを持つことができた。 ではこのstateにどうやってアクセスするのか?

MySlidetとMyScheduelをつなげる必要がある

  • Provider.of

MySliderからMyScheduleに伝える必要があるが、そうするにはProvider.ofを使います。

final schedule = Provider.of<MySchedule>(context);
return Slider(
    value: schedule.stateManagementTime,
    onchanged: (value) => schedule.stateMangementTime = value
)

Provider.ofでMyScheduleインスタンスを取得して、状態の変数をセットします。

onChangeでMySceduleのstateManagermetTimeにセットすることで、MySChedule(のおstateMangementTime)を監視しているオブジェクトに通知されます。

  • consumer widget

Consumerで変化を受け取ります。

  @override
  Widget build(BuildContext context) {
    return Consumer<MySchedule>(
        builder: (context, scheduler, _) =>
            PieChart(dataMap: createData(scheduler.stateManagementTime)));
  }

Consumerのbuilderの引数に状態が変更されたあとのMyScheduleインスタンスが渡ってくるので、そのインスタンスから状態を取得しています。

これでMySliderのスライダーを動かすと、チャートが変更されることがわかるかと思います。

セッションのコードを動かしてみたのはこちらにあります。(セッション中のチャートを厳密に再現するのはめんどくさかったので、適当なチャートです。)

https://github.com/kwmt/flutter_state_management/tree/provider

Googleではprovide (scoped model version 2)を公開したが、providerのほうがmuch betterだったのでproviderを使っている

more real word approach

diposeable

providerはデフォルトでdisposeできる仕組みをもっている dispose callback

Provider<Foo>(
    builder: (context) => Foo(),
    dispose: (context, foo) => foo.dispose(),
)

ツリーではdisposeしなくていい、適切にdisposeされる



comments powered by Disqus