SlideShare a Scribd company logo
1 of 114
Download to read offline
Flutter のリアクティブ戦略
setState 〜 Redux まで
Build reactive mobile apps with Flutter
Google I/O '18 セッション 概要 
自己紹介
名前
robo (兼高理恵)
好きなもの
モバイル端末
おしごと
アプリの設計から実装まで
2
[Session] Build reactive mobile apps with Flutter
2018/05/10 (Thu) 10:30AM - 11:30AM Stage 3
https://events.google.com/io/schedule/?section=may-8&sid=dab2bf45-6e44-4605-a997-9d446f95ef38
Build reactive mobile apps with Flutter (Google I/O '18)
https://www.youtube.com/watch?v=RS36gBEp8OI
3
この LT は、Google I/O 2018 セッション
Build reactive mobile apps with Flutter の概要です。
セッション中のスライドを利用していますが、  
構成都合のためにビデオ中での説明からの意訳や 
独自の説明に置き換えている点に御留意ください。
ビデオではコードの流れが読みにくいため
Fillips さんのサンプル・プロジェクトも併せて御確認ください。
filiph/state_experiments
https://github.com/filiph/state_experiments
4
5
filiph/state_experiments
https://github.com/filiph/state_experiments
state_experiments
リポジトリについて
セッション講演者
Fillips さんの補助資料です。
基本的な setState() による再描画や、
Stream をつかったデータ取扱による
リアクエティブなイベント反応方法まで
設計パターンごとの具体的実装が
確認できます。
state_experiments リポジトリの使い方
利用方法
下記コマンドでリポジトリをクローンして、
クローン先の shared ディレクトリ(プロジェクト)を
IntelliJ IDE で開いてください。
設計パターンごとのアプリをビルドしたい場合は、
lib/main.dart の main 関数の設計種別を表す flavor の
enum 値の書き換えが必要です。
例) final flavor = Archtecture.redux; ⇒ final flavore = Archtecture.bloc;
6
$ cd サンプルディレクトリ
$ git clone https://github.com/filiph/state_experiments.git
【補足事項】
● Android アプリをビルドする場合は、 gradle-wrapper.jar を追加してください。
プロジェクトの android/gradle/wrapper には、gradle-wrapper.jar が含まれていません。
このため適当なプロジェクトを作成して、そこから gradle-wrapper.jar をコピーしてください。
● サンプルプロジェクトは、 master channel のSDKでビルドしてください。
自分の環境の確認や master への切替は、下記を御参照ください。
7
# 現在どの channel が選択されているかチェック
$ flutter channel
Flutter channels:
beta
dev
* master
# channel を master に切り替え
$ flutter channel master
# channel の確認&SDKのアップデート
$ flutter doctor
セッションの構成
8
セッションの構成
1. Flutter & state
Flutter と State の基礎
2. State & the widget tree
ウィジェットの State アクセス方法
3. Reactive with streams
ストリームを使った State のエレガントな管理法
9
以上の3ステップから構成されています
1. Flutter & state
 Flutter と State の基礎
10
11
 Flutter の Hello World とも言える、
 FAB タッチでカウントアップされるアプリでおなじみな、
最も基本的なイベント入力から出力を行う方法です。 
12
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _increment() {
setState(() {
_counter += 1;
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
body: …,
children: <Widget>[
…,
new Text(
'$_counter', …,
),
],
),
floatingActionButton: new Incrementer(_increment),
);
}
}
class Incrementer extends StatefulWidget {
final Function increment;
Incrementer(this.increment);
@override
IncrementerState createState() {
return new IncrementerState();
}
}
class IncrementerState extends State<Incrementer> {
@override
Widget build(BuildContext context) {
return new FloatingActionButton(
onPressed: widget.increment,
…,
);
}
}
以降のスライドのコード中核は、
このような構成になっています。
詳しくは、state_experiments サンプルの
hello_world ディレクトリ(プロジェクト)の
lib/main.dart を参照ください。
13
Flutter では、
全てのUIがウィジェットです。
このようなウィジェットツリーで…
14
ここ(*1)
で State (カウンター状態) を
必要とします。
(*1)
MyHomePage の Text ウィジェットと
Incrementer の FloatingActionButton
ウィジェットの onPressed
15
State (カウンター状態) は、
ここ(*1)
にあります。
(*1)
MyHomePage ウィジェットの
_MyHomePageState
16
よって親ウィジェットの
State (カウンター状態) は、
子ウィジェットから参照可能にしな
ければなりません。
そのためには、
クロージャを使って参照 (*1)
させたり、
StatefulWidget のコンストラクタ引数に State
を渡してあげる(*2)
必要があります。
(*1)
MyHomePage の Text ウィジェット
(*2)
Incrementer ウィジェット
17
サンプルでは、
State のカウンタ値を +1 して
setState()で再描画を行わせる関数(*1)
を
コンストラクタに渡しています。
(*1)
_MyHomePageState の _increment
親ウィジェットの関数をコールするための
古典的なコールバックメソッド
呼び出した先でなく、
setState() の所有ウィジェット(*2)
が
再描画されることに注意してください
(*2)
MyHomePage ウィジェット
この手法では、State を伝播させる経路の
全ウィジェットで State を扱わせてしまいます。
MyHomePageから
FAB も Text も再描画
されてしまいます。
課題点
この手法は、State を伝播させる経路の
全ウィジェットで State を扱わせてしまい、
再利用や関心事の分離を壊し、不要なUI更新も招くため、
以下の課題を抱えています。
● 必要なウィジェットだけに State をアクセスさせること。
● 再描画が必要なウィジェットのみを再描画させること。
18
19
小さなアプリでは簡易ですが、     
  大きなアプリでは複雑になるので、 
         評価はこうなります。
2. State & the widget tree
  ウィジェットの State アクセス方法
20
21
経路途中のウィジェット に State/状態 を扱わせず、
参照するもののみに State/状態 を公開する方法として、
     InheritedWidget と ScopedModel を紹介します。
22
Flutter の InheritedWidget クラスを継承したウィジェットを作れば、
 State/状態 の保持とウィジェットツリー下流への伝播および、
  State/状態 の参照と更新も実現できます。
InheritedWidget
https://docs.flutter.io/flutter/widgets/InheritedWidget-class.html
23
このウィジェットを
State (状態)を保持する
InheritedWidget を継承した
ウィジェットに置き換えます。
参照側のウィジェットでは、
State を返すスタティックメソッド
of(context) を使って、
ビルドコンテキストから、
直接 State (状態)にアクセスします。
InheritedWidget の具体的な使い方は、 state_experiments サンプル
shared ディレクトリ(プロジェクト)の lib/src/bloc_start を参照ください。
24
状態提供ウィジェット・クラスの定義
InheritedWidget を継承し、状態(state)プロパティを持った、
状態提供ウィジェット・クラス (MyInheritedWidget) を定義する
状態提供ウィジェットの生成と、状態の初期値設定
状態提供ウィジェットツリーの生成と状態の設定を
ラップするウィジェットの build() メソッドで行います
状態参照ウィジェットでの状態の参照方法
状態提供ウィジェットのスタティックメソッドと引数の BuildContext から
状態提供ウィジェットの参照を取得して、状態プロパティにアクセスしています
InheritedWidget を利用する実装手順
課題点
InheritedWidget クラスを
継承させたウィジェットであれば、
保持している State をウィジェットツリーの下流で
BuildContext を介して参照や更新することができます。
● ですが State の参照や更新ができても、
         効率的な再描画はもっていません。
25
26
評価はこうなります。
27
InheritedWidget を包含して拡張した、
外部パッケージ(*1)
の ScopedModel クラスを使えば、
State の参照や更新と、ツリーの一部の効率的な再描画もできます。
(*1)
scoped_model 0.2.0 https://pub.dartlang.org/packages/scoped_model
28
ScopedModel を利用する実装手順
ScopedModel は、
InheritedWidget と同じように利用できます。
State へのアクセスには、
ScopedModelDescendant を使うところが異なります。
29
scoped_model パッケージ
親ウィジェットから下流に
データモデルを簡単に渡すことを
可能にする一連のユーティリティ。
モデルの更新時に、
モデルを使用する全ての
子ウィジェットの再描画も行えます。
Example のコードが
セッション内容の参考になると思います。
scoped_model 0.2.0
https://pub.dartlang.org/packages/scoped_model
ScopedModel
ScopedModelDescendant ソースコード先
brianegan/scoped_model
scoped_model/lib/scoped_model.dart
https://github.com/brianegan/scoped_model/
blob/master/lib/scoped_model.dart
これから先は、状態の伝播を少し複雑にして説明するため、
ショッピングカートを模したアプリを使った、
State/状態 ハンドリングの説明になります。
30
31
ショッピングカートアプリの特徴
● State/状態を処理するウィジェットは3つ
● 全てのウィジェットが StatelessWidget です
32
State/カート状態 をハンドルするウィジェットたち
1. 四角い Product グリッド をタッチすると、
カートにタッチした 商品 が入ります。
2. 画面右上の Cart Button には、
カートに入った 商品 数が表示されます。
3. Cart Button をタッチすると Cart Page に
カートに入れた 商品リストを表示します。
商品リスト/Cart Page は、
カートに何も入っていなければ、Empty を表示
33
0
商品リスト/Cart Page は、
カートに商品が入っていればリストアップします。
34
3
35
Product グリッドは、
タッチされるとカートに
商品を追加します。
Cart Button は、
カートに入った
商品総数を表示します
Cart Page は、
Cart Button がタッチされると
カートに入っている商品リストを表示します。
ショッピングカートのウィジェットツリー
ショッピングカート・アプリでの
ScopedModel を使った State アクセス
36
このステップのコードは、
state_experiments サンプル
shared ディレクトリ(プロジェクト)の 
lib/src/scoped のソースを参照ください。
state_experiments サンプルの lib/src/scoped より抜粋
37
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScopedModel<CartModel>(
model: CartModel(),
child: MaterialApp(
…,
home: CatalogHomePage(),
routes: <String, WidgetBuilder>{
CartPage.routeName: (context) => CartPage(),
},
…,
),
);
}
// 継承元の Model は、
// scoped_model パッケージの抽象クラスです。
class CartModel extends Model {
final _cart = Cart();
List<CartItem> get items => _cart.items;
int get itemCount => _cart.itemCount;
void add(Product product) {
_cart.add(product);
notifyListeners(); // 全参照ウィジェットを再描画
}
}
カートへの商品追加時に、
カート状態を参照する全ウィジェット
(ScopedModelDescendant のウィジェット)を再
描画させます。
38
class CartPage extends StatelessWidget {
static const routeName = '/cart';
@override
Widget build(BuildContext context) {
return Scaffold(
…,
body: ScopedModelDescendant<CartModel>(
builder: (context, _, model) {
if (model == null || model.items.isEmpty) {
return Center(
child: Text('Empty', style: Theme.of(context)
.textTheme.display1),
);
}
return ListView(
children: model.items.map((item) =>
ItemTile(item: item)).toList());
}),
);
}
}
class CatalogHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
…,
actions: <Widget>[
ScopedModelDescendant<CartModel>(
builder: (context, child, model) => CartButton(
itemCount: model.itemCount,
onPressed: () {
Navigator.of(context).pushNamed(
CartPage.routeName);
},
),
)
],
),
body: ProductGrid(),
);
}
}
39
class ProductGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 2,
children: catalog.products.map((product) {
return ScopedModelDescendant<CartModel>(
rebuildOnChange: false,
builder: (context, child, model) =>
ProductSquare(
product: product,
onTap: () => model.add(product),
),
);
}).toList(),
);
}
}
● ProductSquare (商品グリッド)は、
初期表示から変わらないので、
再描画する必要はありませんが …
タッチされたときに
カートに商品を追加するため、
CartModel を参照しているので、
ScopedModel の再描画対象になります。
● このため
rebuildOnChange フラグを false にして、
CartModel#add での notifyListeners() で、
再描画されないようにする必要があります。
ScopedModel サンプル実装のポイント
1. 状態モデル、
状態提供ウィジェット、状態参照ウィジェットの定義
2. 状態モデルの参照 / 状態変更
3. 全ての状態参照ウィジェットの再描画
4. 状態参照ウィジェットでの再描画の抑止
5. 補足
状態提供ウィジェットは、状態参照よりもウィジェットツリー上流にある必要があります。
ウィジェットは、StatelessWidget で構いません。(StatefulWidget である必要はありません )
40
状態モデル、状態提供ウィジェット、状態参照ウィジェットの定義
● 状態モデル
取り扱いたい State/状態 を提供する
scoped_model パッケージの Model を継承したクラス
● 状態提供ウィジェット
build() メソッドの返値として、
あるいは返値の Widget 型のプロパティに対して、
ScopedModel<状態モデル> を割り当てる ウィジェット
● 状態参照ウィジェット
build() メソッドの返値として、
あるいは返値の Widget 型のプロパティに対して、
ScopedModelDescendant<状態モデル> を割り当てる ウィジェット
41
CartModel クラスを参照
MyApp クラスを参照
CatalogHomePage, CartPage, ProductGrid クラスを参照
状態モデルの参照 / 状態変更
● 状態モデルの参照 / 状態変更
状態参照ウィジェットの builder() メソッド引数の model に
状態モデル への参照が割り当てられるので、model 引数を介して、
必要なプロパティの参照やメソッドの実行が行えます。
サンプルではウィジェット構築時やタッチイベントで使っています。
CatalogHomePage, CartPage, ProductGrid クラスを参照  
42
再描画 / 再描画抑止
● 全ての状態参照ウィジェットの再描画
状態モデルの再描画を行いたいイベントのハンドル先で、
notifyListeners() メソッドを実行させます。
ProductSquare:onTap, CardModel#add(Product) 参照  
● ウィジェット再描画抑止
ウィジェットの再描画の必要のない状態参照ウィジェットの
ScopedModelDescendant のプロパティ rebuildOnChange に false を
設定します。
ProductGrid#build() 参照  
43
課題点
ScopedModel クラスを使えば、
State をウィジェットツリーの下流で参照&更新できます。
State を参照する全ウィジェットの再描画もできますが、
以下の課題を抱えています。
● State の特定プロパティへの更新を検出(対処)できない。
● 再描画させたくないウィジェットには、
その特定と再描画抑止フラグの設定が必要である。
44
45
評価はこうなります。
3. Reactive with streams
ストリームを使った    
State のエレガントな管理法  
46
47
Stream と Obsevables の概念は密接に関係していて同じように扱えます。
48
アプリケーション開発における、
すべての対話(データ入出力)は、ストリーム(data-flow/データの流れ)です。
ユーザ入力、システムイベント、
Web API のようなネットワークを介した外部との対話は、
非同期イベントのストリームです。
data-flow
どこからどこにデータが渡され
その過程でデータの扱いがどの
ように変わるかの概念
49
重要なことは、
UI更新の全てが非同期イベントのストリームであることです。
UIプログラミングが、
非同期イベントのストリームを管理することには意味があります。
50
Dart言語は、昔からストリームをサポートしています。
51
Dart言語のストリームには、
ストリーム変換、マッピング、折り畳みなど有用なメソッドがあります。
52
Dart言語には、
非同期ジェネレータのようなキーワード async や await for や yield があり、
言語レベルでストリームをサポートしています。
53
Dart言語において、ストリームは概念として広く使われています。
54
そして Reactive Extensions (Rx / ReactiveX) と   
Reactive Programming の概念も持っています。
55
rxdart と呼ばれるパッケージがこれらを基にして作られています。
これはストリームの上に Reactive Extensions (Rx) を築き上げます。
56
Dart 言語のストリームをウィジェットに適用させるため、
Flutterには、StreamBuilderというウィジェットがあります。
57
StreamBuilder は、
データ入力用ストリームと builder() メソッドを持ち、
ストリームにデータが流れこむたびに再描画させることができます。
58
このようなウィジェットツリーを持つアプリがあるとします。
アプリケーション構築のための
アーキテクチャパターンを紹介するため…
59
ユーザー入力のウィジェットをいくつか持っているので、
ユーザーからの非同期イベントのストリームも持っています。
60
そして、ウィジェットツリー内の他のウィジェットで、
状態が変わるたびに再描画を試みるとします。
61
単に両者を結びつけるだけでは不十分ですから、
必要な処置を行うビジネスロジックを設けます。
62
入力を持つウィジェットからのイベントは、
ビジネスロジックの StreamController#Sink<Event>.add() に通知します。
あらかじめ StreamCntroller#Stream.listen() では、
対応する処理(入力イベントから最新データを作製する)を行うイベントハンドラを登録しておきます。
63
ビジネスロジックでは、
登録済みのイベントハンドラで入力イベントから最新データを作成/更新し、
出力先のウィジェットでデータの変更を監視している
StreamBuilder#Stream<Data> プロパティが反応できるようにします。
出力先ウィジェットの StreamBuilder では、
引数で渡された最新データを反映させる UI 構築ハンドラを builder() メソッドに定義しています。
64
こうすることで出力先のウィジェットのUIを最新の内容で再描画させます。
出力先ウィジェットの StreamBuilder#stream プロパティ更新により、
StreamBuilder#builder() の UI 構築ハンドラが実行され、再描画されます。
65
重要なのは、
ビジネスロジックに任意の側面ごとにストリームを公開することと、
出力ウィジェットが自分の関心のある側面のストリームのみ購読することです。
こうすることで、自分の関心のある側面が変更された場合のみ再描画させます。
側面 ⇒ State の構成要素
例)カートに入れられた
アイテム総数やアイテムのリスト
66
ビジネスロジックには、入力ストリーム と 出力ストリーム があります。
ストリーム ⇒ data-flow / データの流れ
67
この入力と出力のストリームは、Dart言語でどのように実装するのでしょう。
68
先ずは、入力イベントをハンドルする Sink オブジェクトを設定します。
69
次に、データ出力の Streamオブジェクト を設定します。
70
ショッピングカート・アプリの例では、
カートに、商品(Product)を追加する addition という入力と、
アイテム数の変化ごとに更新される itemCount という出力になります。
実際の実装で利用する、
StrteamController#Sink<Event>、
StreamBuilder#Stream<Data> の
説明が省かれていることに注意!
71
入力と出力の対応により、ビジネスロジックのコンポーネントができました。
72
Google 内部では、このようなビジネスロジックのコンポーネントを…
73
bloc (ブロック)と略称しています。
1. Sink で Widget の入力イベントを取得し
2. Stream に最新データの出力を行い
3. Widget を最新化(再描画)するような、
4. (ある責務をまとめた)任意の実装単位 ⇒
      ビジネスロジックのコンポーネント
             Business Logic Component を
  BLoC (Business Logic Component) と呼んでいるそうです。
Build reactive mobile apps with Flutter (Google I/O '18)
https://youtu.be/RS36gBEp8OI?t=1387 (22:00頃)
74
 Flutter での BLoC パターンやその思想については、
 Dart Meetup Tokyo 管理者の laco 氏のブログ記事が
参考になります。
 FlutterのBLoCパターンをAngularで理解する
 https://lacolaco.hatenablog.com/entry/2018/05/22/194805
Dart Meetup Tokyo
https://dartisans-jp.connpass.com/
75
ショッピングカート・アプリでの
Stream と Sink を使ったストリーム処理
76
このステップのコードは、
state_experiments サンプル
shared ディレクトリ(プロジェクト)の 
lib/src/bloc のソースを参照ください。
state_experiments サンプルの lib/src/bloc より抜粋
77
void main() { runApp(MyApp()); }
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CartProvider(
child: MaterialApp(
…,
home: MyHomePage(),
routes: <String, WidgetBuilder>{
BlocCartPage.routeName: (context) =>
BlocCartPage()
},
),
);
}
}
class CartProvider extends InheritedWidget {
final CartBloc cartBloc;
CartProvider({
Key key,
CartBloc cartBloc,
Widget child,
}) : cartBloc = cartBloc ?? CartBloc(),
super(key: key, child: child);
@override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static CartBloc of(BuildContext context) =>
(context.inheritFromWidgetOfExactType(CartProvider) as
CartProvider).cartBloc;
}
カート状態 CartBloc (business logic含) は
InheritedWidget を継承させた CartProvider
の of() メソッドを介して、
ウィジェットツリー下流から
取得可能になっています。
78
import 'package:rxdart/subjects.dart';
class CartAddition {
final Product product;
final int count;
CartAddition(this.product, [this.count = 1]);
}
class CartBloc {
final Cart _cart = Cart();
final BehaviorSubject<List<CartItem>> _items =
BehaviorSubject<List<CartItem>>(seedValue: []);
final BehaviorSubject<int> _itemCount =
BehaviorSubject<int>(seedValue: 0);
final StreamController<CartAddition>
_cartAdditionController =
StreamController<CartAddition>();
…右に続く
CartBloc() {
_cartAdditionController.stream.listen((addition) {
int currentCount = _cart.itemCount;
_cart.add(addition.product, addition.count);
_items.add(_cart.items);
int updatedCount = _cart.itemCount;
if (updatedCount != currentCount) {
_itemCount.add(updatedCount);
}
});
}
Sink<CartAddition> get cartAddition =>
_cartAdditionController.sink;
Stream<int> get itemCount => _itemCount.stream;
Stream<List<CartItem>> get items => _items.stream;
void dispose() {
…
}
}
79
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cartBloc = CartProvider.of(context);
return Scaffold(
appBar: AppBar(
title: 'Bloc',
actions: <Widget>[
StreamBuilder<int>(
stream: cartBloc.itemCount,
initialData: 0,
builder: (context, snapshot) => CartButton(
itemCount: snapshot.data,
onPressed: () { … },
),
)
],
),
body: ProductGrid(),
);
}
}
class ProductGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cartBloc = CartProvider.of(context);
return GridView.count(
crossAxisCount: 2,
children: catalog.products.map((product) {
return ProductSquare(
product: product,
onTap: () {
cartBloc.cartAddition
.add(CartAddition(product));
},
);
}).toList(),
);
}
}
class BlocCartPage extends StatelessWidget {
BlocCartPage();
static const routeName = "/cart";
@override
Widget
80
class BlocCartPage extends StatelessWidget {
BlocCartPage();
static const routeName = "/cart";
@override
Widget build(BuildContext context) {
final cart = CartProvider.of(context);
return Scaffold(
appBar: AppBar(
title: Text("Your Cart"),
),
body: StreamBuilder<List<CartItem>>(
stream: cart.items,
builder: (context, snapshot) {
if (snapshot.data == null || snapshot.data.isEmpty) {
return Center(
child: Text('Empty',
style:Theme.of(context)
.textTheme.display1));
}
…右に続く
return ListView(
children: snapshot.data
.map((item) => ItemTile(item: item))
.toList());
}));
}
}
Sink と Stream オブジェクトに関連する
クラスとフィールドとメソッドのみ、
型ごとに色を分けています。
ショッピングカート・アプリ例の
入力イベント ~ UI 最新化までの処理フローについては、
スライド 62 ~ 65 を参考に、lib/src/bloc より抜粋した
コードを御参照ください。
81
Productグリッドのタッチ
  ⇒ CartへのProduct追加
  ⇒ CartButtonアイテム総数UI の再描画のフローなら、
・ProuctGrid 内の onTap ハンドラ、
・CartBloc の StreamController _cartAdditionCotroller や
 _cartAdittionController.stream.listen() に_cartAdditionController.sink、
・MyHomePage での StreamBuilder<int>() 辺りが参考になると思います。
ビジネスロジック コンポーネントでの
ポイント
● 入力〜出力のフローの始端と終端のみ公開し、依存の分離を高めます。
特定の入力イベント受付となる Sink<Event>オブジェクト と、
特定の出力データ窓口となる Stream<Data>オブジェクト のみ公開します。
入力ウィジェットからのイベントの通知やハンドル方法や、
出力ウィジェットへの最新データによる UI 最新化通知は、隠蔽します。
● bloc では、特定の実装方法はありません。
サンプルでは、StreamController<Event>とStreamBuilder<Data>を使って、
入力イベントのハンドルと出力データによるUI 最新化を行っていますが、
bloc では、イベント入力とデータ出力のインターフェースが、
Sink<Event> と Stream<Data> であればよく、
特定の実装方法はないそうです。
82
StreamController を使った
入力イベントの通知とハンドルのポイント
● StreamController<Event>
Event 入力をハンドルして必要な処理を行わせるコントローラの定義
stream:Stream<Event> と sink:Sink<Event> プロパティを所有しています。
● StreamController#stream.listen((Event){ …対応処理… })
Event対応ハンドラの登録
ハンドラでは、最新のEvent情報に依存する各種 Data の更新を行う。
● StreamController#sink.add(最新Event)
Event最新情報の入力イベントの通知
最新Event を引数に与えて、Event対応ハンドラを実行させるメソッド。
83
StreamBuilder を使った
最新データによる UI 最新化通知のポイント
● StreamBuilder<Data>
最新Data をUI に反映(Data と UI を同期)させる StreamBuilder の定義
StreamBuilder は、stream:Stream<Data> プロパティを所有しています。
● StreamBuilder#builder((BuildContext, AsyncSnapshot<Data>){ …UI構築… })
UI構築ハンドラの登録
引数の最新Data スナップショットから、最新Dataを反映した UI の構築を行う。
● StreamBuilde#stream
Dataの変更監視プロパティ
プロパティ stream の監視先が最新Dataに更新されると、
最新Dataを引数に与えて、UI構築ハンドラが実行されます。
84
StreamBuilder<T> class
https://docs.flutter.io/flutter/widgets/StreamBuilder-class.html
プロパティとして stream:Stream<T> と_snapshot:AsyncSnapshot<T> を持ち、
ストリームのデータが変更されると、最新のスナップショットに基づいて
ウィジェットを再構築させるクラス。
85
StreamController<T> class
https://docs.flutter.io/flutter/dart-async/StreamController-class.html
ストリームを制御するコントローラ・クラス
プロパティとして stream:Stream<T> と sink:StreamSink<T> を持ち、
stream へのリスナー関数の登録と、sink へのデータ追加(更新)により、
データ追加(更新)やエラー発生のイベントをストリームに
プッシュすることができます。
またストリームが一時停止中かサブスクライバを持つか否かの確認や、
いずれかがが変更時にコールバックを取得できます。
86
Stream<T> class
https://docs.flutter.io/flutter/dart-async/Stream-class.html
非同期イベントのデータを扱える(連鎖的に処理できる)ようにするクラス。
非同期に提供される/変化するデータを扱うため、
そのイベントごとのデータ処置/変換を行う関数を
宣言的に定義できるようにします。
イベント対応の手法としては、
await for キーワードによるイベント契機の待機指定や、
listen(関数)によるコールバック関数のリスナ登録が利用できます。
87
StreamSink<T> class
https://docs.flutter.io/flutter/dart-async/StreamSink-class.html
同期的あるいは非同期的なデータの追加/変更のイベント契機の受付となり、
そのデータをストリームに伝える抽象クラス。
Sink<T> class
https://docs.flutter.io/flutter/dart-core/Sink-class.html
データの追加/変更のイベント契機の受付となる抽象クラス。
(データ値の直接受け取りだけでなく、取得 /生成する処置関数も受け取ることもできます。)
88
BehaviorSubject<T> class
https://github.com/ReactiveX/rxdart/blob/master/lib/src/subjects/behavior_subject.dart
rxdart パッケージの特殊な StreamController クラス
追加/変更されるデータをObservable(監視対象)とする
Stream を提供するようです。
注)ドキュメントが見つからなかったため詳細は不明です。
89
90
rxdart 0.16.7
https://pub.dartlang.org/packages/rxdart
rxdart パッケージ
RxDart は ReactiveX をベースにした
Dart 言語用のリアクティブ関数プログラミング
ライブラリです。
RxDartは、Dart の Streams API を置き換える
ものではなく、それを基にして機能を追加します。
rxdart リポジトリ
ReactiveX/rxdart
https://github.com/ReactiveX/rxdart
これから先は、
 ビジネスロジック コンポーネント (bloc) を使って、
  入力イベントと出力データを
  役割ごとに別々に分離できるようになったことで、
       どんなことが便利になるのかを紹介します。
91
92
イベント入力〜データ出力を一連のストリームに分離することで、
Flutter UI は、コンポーネントで何をしているのか
                 気にする必要がなくなりました。
93
さらに bloc にしたことで、イベント入力に対応する
別のデータ出力 Stream を追加することだってできます。
カートの総コストやアイテムのリストは、
更新されるたびに UIも最新化したいでしょうから、
総コスト totalCost や アイテムのリスト items も追加できます。
94
でも、総コスト totalCost を
整数データのストリームにすることには問題がありそうです。
たしかにコンポーネント内では意味を持ちますが…
95
ウィジェット側では、
このように数値を文字列に変換しなくてはなりません、
これはビジネスロジックなので、ビューにあってはいけないでしょう。
96
だからビジネスロジックコンポーネント CartBloc に戻って…
97
総コスト totalCost を
フォーマット済みの String の Stream に変更しましょう。
bloc ですから、出力データ型の変更だってできます。
98
コンポーネント内でのロジックは、
先のウィジェットでの数値から文字列への変換のような
既にある同じビジネスロジックを再利用するだけですみます。
99
新しい入力イベントを作ることだってできます。
ロケール locale という新しい入力イベント Sink<Locale> を追加すれば…
ユーザは、
米国からEUの店舗に切り替えることだってできるでしょう。
100
bloc ですから、locale の入力イベントから
出力データ totalCost にストリームを繋ぐことだってできます。
内部的には数値の変化がなくても、
totalCost の文字列を更新 ⇒ UI最新化 ⇒ができます。
totalCost の変更が何処で発生しようとも、対応できるのです。
101
StreamController と StreamBuilder を使えば、
入力イベントの通知やハンドリング(最新データ作製)と、
出力データの監視とUI更新を実現することができます。
イベントとデータのストリーム化は、他の方法よりも有益です。
改良点
● カート状態(*1)
は、InheritedWidget パターンを利用して
ウィジェットツリーの下流でも取得可能にした。
● カート状態を役割別の入力イベントと出力データの
ストリームに小分けして細かな参照更新を可能にした。
● ウィジェットは、UI更新に関係する出力データを
購読することで、その変更でのみ再描画可能になった。
(*1)
CartBloc には、状態だけでなくビジネスロジックのコンポーネントも含む。
102
103
評価はこうなります。
ビジネスロジックに
イベントのハンドルや
データ更新や監視およびUI更新の仕組みを移設して、
イベント入力〜データ出力をストリームにすることで、
ストリームを介してイベントの投入やデータの更新を行う
エレガントなパターンができました。
104
その他の
State管理の選択肢
105
● StatefulWidget と setState() は、
浅いツリーかつ、アプリがシンプルであれば
問題ありません。
● ScopedModelは、
モデルが比較的簡単で、
任意の深さのツリー上での状態更新に適しています。
● reduxパターンが好きな人には、
コミュニティによって作られた優れた redux の実装…
Dart言語用の redux パッケージと
Flutter用の Flutter Redux があります。
106
【補足】 Flutter Redux について
セッションでの説明がないため詳細は不明ですが、
state_experiments サンプルには、
Flutter Redux を使ったサンプルも含まれています。
よろしければ、   
state_experiments サンプル shared ディレクトリ(プロジェクト)の  
lib/src/redux のソースも御参照ください。
107
108
redux 3.0.0
https://pub.dartlang.org/packages/redux
redux パッケージ
flutter_reduxパッケージを使用すれば、
Flutterと組み合わせることができます。
Dart言語 redux リポジトリ
johnpryan/redux.dart
https://github.com/johnpryan/redux.dart
109
flutter_redux 0.5.1
https://pub.dartlang.org/packages/flutter_redux
flutter_redux パッケージ
Flutterウィジェットを構築するために
Reduxストアを簡単に使用できる
ユーティリティのセット。
flutter_redux リポジトリ
brianegan/flutter_redux
https://github.com/brianegan/flutter_redux
結論
110
Flutter のウィジェットを
ストリームと組み合わせて使用することで、
データの流れを処理するリアクティブな方法が得られます。
データが変更されたときのみ、UIの更新を処理できます。
Dart言語の Streamsと rxdart パッケージを
アプリの状態管理の選択肢とすることを強くお勧めします。
111
112
Flutter のウィジェットを
ストリームと組み合わせて使用することで、
データの流れを処理するリアクティブな方法が得られます。
データが変更されたときのみ、UIの更新を処理できます。
Dart言語の Streamsと rxdart パッケージを
アプリの状態管理の選択肢とすることを強くお勧めします。
ご清聴、
ありがとうございました。 113
A Special thanks to Mr Fillip!
[Meetup] Flutter developers
2018/05/10 (Thu) 12:10PM - 12:40PM Community lounge
https://events.google.com/io/schedule/?section=may-9&sid=ba156a8e-3d16-4b29-9788-878f1f12ead9
114

More Related Content

What's hot

契約プログラミング
契約プログラミング契約プログラミング
契約プログラミング
Oda Shinsuke
 

What's hot (20)

Go1.8 for Google App Engine
Go1.8 for Google App EngineGo1.8 for Google App Engine
Go1.8 for Google App Engine
 
DI(依存性注入)について
DI(依存性注入)についてDI(依存性注入)について
DI(依存性注入)について
 
マスター・オブ・Reflectパッケージ
マスター・オブ・Reflectパッケージマスター・オブ・Reflectパッケージ
マスター・オブ・Reflectパッケージ
 
条件式評価器の実装による管理ツールの抽象化
条件式評価器の実装による管理ツールの抽象化条件式評価器の実装による管理ツールの抽象化
条件式評価器の実装による管理ツールの抽象化
 
メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法
メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法
メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法
 
Pythonと型チェッカー
Pythonと型チェッカーPythonと型チェッカー
Pythonと型チェッカー
 
Go入門
Go入門Go入門
Go入門
 
マスター・オブ・goパッケージ
マスター・オブ・goパッケージマスター・オブ・goパッケージ
マスター・オブ・goパッケージ
 
Python と型ヒント (Type Hints)
Python と型ヒント (Type Hints)Python と型ヒント (Type Hints)
Python と型ヒント (Type Hints)
 
PHPでマルチスレッド
PHPでマルチスレッドPHPでマルチスレッド
PHPでマルチスレッド
 
GAE/GoでWebアプリ開発入門
GAE/GoでWebアプリ開発入門GAE/GoでWebアプリ開発入門
GAE/GoでWebアプリ開発入門
 
dezainn
dezainndezainn
dezainn
 
メルカリ・ソウゾウでは どうGoを活用しているのか?
メルカリ・ソウゾウでは どうGoを活用しているのか?メルカリ・ソウゾウでは どうGoを活用しているのか?
メルカリ・ソウゾウでは どうGoを活用しているのか?
 
Goのパッケージ構成で 試行錯誤してみた話 ~ Gocon 2015 Summer
Goのパッケージ構成で 試行錯誤してみた話 ~ Gocon 2015 SummerGoのパッケージ構成で 試行錯誤してみた話 ~ Gocon 2015 Summer
Goのパッケージ構成で 試行錯誤してみた話 ~ Gocon 2015 Summer
 
Code ignitertalk 01
Code ignitertalk 01Code ignitertalk 01
Code ignitertalk 01
 
C# コーディングガイドライン 2013/02/26
C# コーディングガイドライン 2013/02/26C# コーディングガイドライン 2013/02/26
C# コーディングガイドライン 2013/02/26
 
契約プログラミング
契約プログラミング契約プログラミング
契約プログラミング
 
今からでも遅くないC#開発
今からでも遅くないC#開発今からでも遅くないC#開発
今からでも遅くないC#開発
 
デザインパターン(初歩的な7パターン)
デザインパターン(初歩的な7パターン)デザインパターン(初歩的な7パターン)
デザインパターン(初歩的な7パターン)
 
DartPad+CodePenで、Flutterを体験してみよう
DartPad+CodePenで、Flutterを体験してみようDartPad+CodePenで、Flutterを体験してみよう
DartPad+CodePenで、Flutterを体験してみよう
 

Similar to Flutter のリアクティブ戦略 set state 〜 redux まで

Windows ストア lob アプリ開発のためのガイダンスとフレームワークのご紹介 rev
Windows ストア lob アプリ開発のためのガイダンスとフレームワークのご紹介 revWindows ストア lob アプリ開発のためのガイダンスとフレームワークのご紹介 rev
Windows ストア lob アプリ開発のためのガイダンスとフレームワークのご紹介 rev
Shotaro Suzuki
 
SpringMVCとmixer2で作るWebアプリのキホン 2013-01-24 Spring勉強会 #jsug
SpringMVCとmixer2で作るWebアプリのキホン 2013-01-24 Spring勉強会 #jsugSpringMVCとmixer2で作るWebアプリのキホン 2013-01-24 Spring勉強会 #jsug
SpringMVCとmixer2で作るWebアプリのキホン 2013-01-24 Spring勉強会 #jsug
Y Watanabe
 
モバイルゲームの「大規模な開発」かつ「高頻度の更新」を実現するための開発環境整備の取り組み
モバイルゲームの「大規模な開発」かつ「高頻度の更新」を実現するための開発環境整備の取り組みモバイルゲームの「大規模な開発」かつ「高頻度の更新」を実現するための開発環境整備の取り組み
モバイルゲームの「大規模な開発」かつ「高頻度の更新」を実現するための開発環境整備の取り組み
MorioImai
 
Node.js - JavaScript Thread Programming
Node.js - JavaScript Thread ProgrammingNode.js - JavaScript Thread Programming
Node.js - JavaScript Thread Programming
takesako
 
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
Jumpei Ogawa
 

Similar to Flutter のリアクティブ戦略 set state 〜 redux まで (20)

Try Jetpack
Try JetpackTry Jetpack
Try Jetpack
 
Windows ストア lob アプリ開発のためのガイダンスとフレームワークのご紹介 rev
Windows ストア lob アプリ開発のためのガイダンスとフレームワークのご紹介 revWindows ストア lob アプリ開発のためのガイダンスとフレームワークのご紹介 rev
Windows ストア lob アプリ開発のためのガイダンスとフレームワークのご紹介 rev
 
Pin-point rebuildable and non-rebuild custom widget
Pin-point rebuildable and non-rebuild custom widgetPin-point rebuildable and non-rebuild custom widget
Pin-point rebuildable and non-rebuild custom widget
 
EC-CUBEプラグイン講義
EC-CUBEプラグイン講義EC-CUBEプラグイン講義
EC-CUBEプラグイン講義
 
VerilatorとSystemC
VerilatorとSystemCVerilatorとSystemC
VerilatorとSystemC
 
SpringMVCとmixer2で作るWebアプリのキホン 2013-01-24 Spring勉強会 #jsug
SpringMVCとmixer2で作るWebアプリのキホン 2013-01-24 Spring勉強会 #jsugSpringMVCとmixer2で作るWebアプリのキホン 2013-01-24 Spring勉強会 #jsug
SpringMVCとmixer2で作るWebアプリのキホン 2013-01-24 Spring勉強会 #jsug
 
OpenGLプログラミング
OpenGLプログラミングOpenGLプログラミング
OpenGLプログラミング
 
仕事の手離れを良くする手段としての、静的検査のあるテンプレートエンジン (YATT::Lite talk at 2014 テンプレートエンジンNight)
仕事の手離れを良くする手段としての、静的検査のあるテンプレートエンジン (YATT::Lite talk at 2014 テンプレートエンジンNight)仕事の手離れを良くする手段としての、静的検査のあるテンプレートエンジン (YATT::Lite talk at 2014 テンプレートエンジンNight)
仕事の手離れを良くする手段としての、静的検査のあるテンプレートエンジン (YATT::Lite talk at 2014 テンプレートエンジンNight)
 
モバイルゲームの「大規模な開発」かつ「高頻度の更新」を実現するための開発環境整備の取り組み
モバイルゲームの「大規模な開発」かつ「高頻度の更新」を実現するための開発環境整備の取り組みモバイルゲームの「大規模な開発」かつ「高頻度の更新」を実現するための開発環境整備の取り組み
モバイルゲームの「大規模な開発」かつ「高頻度の更新」を実現するための開発環境整備の取り組み
 
Xamarin.formsで作成する翻訳機能付きtwitterクライアント
Xamarin.formsで作成する翻訳機能付きtwitterクライアント Xamarin.formsで作成する翻訳機能付きtwitterクライアント
Xamarin.formsで作成する翻訳機能付きtwitterクライアント
 
PHP 2大 web フレームワークの徹底比較!
PHP 2大 web フレームワークの徹底比較!PHP 2大 web フレームワークの徹底比較!
PHP 2大 web フレームワークの徹底比較!
 
Jetpack Composeのパフォーマンスの基本
Jetpack Composeのパフォーマンスの基本Jetpack Composeのパフォーマンスの基本
Jetpack Composeのパフォーマンスの基本
 
LabVIEW NXG Web Module Training Slide
LabVIEW NXG Web Module Training SlideLabVIEW NXG Web Module Training Slide
LabVIEW NXG Web Module Training Slide
 
WordPress widget api
WordPress widget apiWordPress widget api
WordPress widget api
 
Node.js - JavaScript Thread Programming
Node.js - JavaScript Thread ProgrammingNode.js - JavaScript Thread Programming
Node.js - JavaScript Thread Programming
 
emc++ chapter32
emc++ chapter32emc++ chapter32
emc++ chapter32
 
Google App Engineでできる、あんなこと/こんなこと
Google App Engineでできる、あんなこと/こんなことGoogle App Engineでできる、あんなこと/こんなこと
Google App Engineでできる、あんなこと/こんなこと
 
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
 
C#の新機能勉強会 ~ C#7、8の新機能を活用して速く安全なプログラムを書こう~
C#の新機能勉強会 ~ C#7、8の新機能を活用して速く安全なプログラムを書こう~C#の新機能勉強会 ~ C#7、8の新機能を活用して速く安全なプログラムを書こう~
C#の新機能勉強会 ~ C#7、8の新機能を活用して速く安全なプログラムを書こう~
 
OSC2011 Androidハンズオン
OSC2011 AndroidハンズオンOSC2011 Androidハンズオン
OSC2011 Androidハンズオン
 

More from cch-robo

Loose and fluffy_ddd_intro
Loose and fluffy_ddd_introLoose and fluffy_ddd_intro
Loose and fluffy_ddd_intro
cch-robo
 

More from cch-robo (12)

Flutter_Forward_Extended_Kyoto-Keynote_Summary
Flutter_Forward_Extended_Kyoto-Keynote_SummaryFlutter_Forward_Extended_Kyoto-Keynote_Summary
Flutter_Forward_Extended_Kyoto-Keynote_Summary
 
go_router が隠してくれるもの
go_router が隠してくれるものgo_router が隠してくれるもの
go_router が隠してくれるもの
 
Introduction_on_designing_test_in_flutter
Introduction_on_designing_test_in_flutterIntroduction_on_designing_test_in_flutter
Introduction_on_designing_test_in_flutter
 
Flutterを体験してみませんか
Flutterを体験してみませんかFlutterを体験してみませんか
Flutterを体験してみませんか
 
Dart言語の進化状況
Dart言語の進化状況Dart言語の進化状況
Dart言語の進化状況
 
明示的アニメで、Flutterアニメーション入門
明示的アニメで、Flutterアニメーション入門明示的アニメで、Flutterアニメーション入門
明示的アニメで、Flutterアニメーション入門
 
Dartでサーバレスサービス
DartでサーバレスサービスDartでサーバレスサービス
Dartでサーバレスサービス
 
Android lint-srp-practice
Android lint-srp-practiceAndroid lint-srp-practice
Android lint-srp-practice
 
Loose and fluffy_ddd_intro
Loose and fluffy_ddd_introLoose and fluffy_ddd_intro
Loose and fluffy_ddd_intro
 
Firebase Test Lab 無料枠を使ってみました。
Firebase Test Lab 無料枠を使ってみました。Firebase Test Lab 無料枠を使ってみました。
Firebase Test Lab 無料枠を使ってみました。
 
ZTE OPEN を日本語化(バージョンアップ)してみる
ZTE OPEN を日本語化(バージョンアップ)してみるZTE OPEN を日本語化(バージョンアップ)してみる
ZTE OPEN を日本語化(バージョンアップ)してみる
 
FirefoxOSで学ぶJavaScript作法
FirefoxOSで学ぶJavaScript作法FirefoxOSで学ぶJavaScript作法
FirefoxOSで学ぶJavaScript作法
 

Recently uploaded

Recently uploaded (7)

業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
 
LoRaWAN スマート距離検出デバイスDS20L日本語マニュアル
LoRaWAN スマート距離検出デバイスDS20L日本語マニュアルLoRaWAN スマート距離検出デバイスDS20L日本語マニュアル
LoRaWAN スマート距離検出デバイスDS20L日本語マニュアル
 
Amazon SES を勉強してみる その22024/04/26の勉強会で発表されたものです。
Amazon SES を勉強してみる その22024/04/26の勉強会で発表されたものです。Amazon SES を勉強してみる その22024/04/26の勉強会で発表されたものです。
Amazon SES を勉強してみる その22024/04/26の勉強会で発表されたものです。
 
LoRaWANスマート距離検出センサー DS20L カタログ LiDARデバイス
LoRaWANスマート距離検出センサー  DS20L  カタログ  LiDARデバイスLoRaWANスマート距離検出センサー  DS20L  カタログ  LiDARデバイス
LoRaWANスマート距離検出センサー DS20L カタログ LiDARデバイス
 
Amazon SES を勉強してみる その32024/04/26の勉強会で発表されたものです。
Amazon SES を勉強してみる その32024/04/26の勉強会で発表されたものです。Amazon SES を勉強してみる その32024/04/26の勉強会で発表されたものです。
Amazon SES を勉強してみる その32024/04/26の勉強会で発表されたものです。
 
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
 
新人研修 後半 2024/04/26の勉強会で発表されたものです。
新人研修 後半        2024/04/26の勉強会で発表されたものです。新人研修 後半        2024/04/26の勉強会で発表されたものです。
新人研修 後半 2024/04/26の勉強会で発表されたものです。
 

Flutter のリアクティブ戦略 set state 〜 redux まで