はじめに
Flutterを始めるとBLoC(BusinessLogicComponent)パターンという言葉を良く見聞きして使わなきゃって強迫観念に駆られて、実際に適用しようとするとStream
やらInheritedWidget
が出てきて「うげげっ」って感じだったりします。
そこでproviderパッケージを利用すれば、その辺を上手に隠匿してくれてBLoCを意識せず利用することができたりします。
providerの使い方
ファイル分割すると分かりづらくなりそうなので、一括で載せちゃいます。
詳細はコードのコメントを参照。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
void main() {
// 共通Stateを初期化
final AppState appState = AppState();
// AppStateはこっから伝搬開始
runApp(MyApp(
appState: appState,
));
}
// アプリケーション共通State
class AppState extends ChangeNotifier {
String message = 'Hello';
void setMessage(String value) {
message = value;
notifyListeners();
}
}
// AppStateはコンストラクタで伝搬
class MyApp extends StatelessWidget {
MyApp({Key key, @required this.appState}) : super(key: key);
final AppState appState;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'provider-test',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: AWidget(
appState: appState,
),
);
}
}
// AWidget用State
class AState extends ChangeNotifier {
int counter = 0;
// インクリメントして更新を通知
void increment() {
counter++;
notifyListeners(); // <- 通知メソッド
}
}
// 最初の画面(AppStateはコンストラクタで伝搬)
// `build`でMultiProviderを利用
class AWidget extends StatelessWidget {
AWidget({Key key, @required this.appState}) : super(key: key);
final AppState appState;
@override
Widget build(BuildContext context) {
return MultiProvider(
// Providerを定義
providers: <SingleChildWidget>[
// AppStateのProvider(共有クラスの場合は`valueコンストラクタ`)
ChangeNotifierProvider<AppState>.value(value: appState),
// AStateのProvider
ChangeNotifierProvider<AState>(
create: (_) => AState(),
),
],
// Consumerを定義
child: Consumer2<AppState, AState>(builder: (
BuildContext context,
AppState appState,
AState aState,
_,
) {
return Scaffold(
appBar: AppBar(
title: const Text('A'),
),
body: Column(
children: <Widget>[
// AppState.messageの表示
Expanded(
child: Container(
alignment: Alignment.center,
child: Text('message is ${appState.message}'),
),
),
// AState.counterの表示
Expanded(
child: Container(
alignment: Alignment.center,
child: Text(
aState.counter.toString(),
),
),
),
// BWidgetへの遷移
// ついでにAState.messageの更新
Expanded(
child: Container(
alignment: Alignment.center,
child: RaisedButton(
child: const Text('Bへ'),
color: Colors.blue,
onPressed: () {
appState.setMessage('World');
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => BWidget(
appState: appState,
),
),
);
},
),
),
),
],
),
// AState.incrementの呼び出し
floatingActionButton: FloatingActionButton(
onPressed: () {
aState.increment();
},
child: Icon(Icons.add),
backgroundColor: Colors.blue,
),
);
}),
);
}
}
// 次の画面(AppStateはコンストラクタで伝搬)
// FutureProviderを使えばAPIの呼び出し等を非同期で流すことができる
class BWidget extends StatelessWidget {
BWidget({Key key, @required this.appState}) : super(key: key);
final AppState appState;
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<AppState>.value(value: appState),
// 非同期Provider(500ミリ秒後にリストを返す)
FutureProvider<List<String>>(
create: (_) async {
// Call API
await Future.delayed(Duration(milliseconds: 500));
return <String>['A', 'B', 'C'];
},
),
],
child: Consumer2<AppState, List<String>>(
builder: (
BuildContext context,
AppState appState,
List<String> strings,
_,
) {
return Scaffold(
appBar: AppBar(
title: const Text('B'),
),
body: Column(
children: <Widget>[
// AppState.messageの表示
Container(
child: Text('message is ${appState.message}'),
),
// stringsの表示
Expanded(
child: ListView.builder(
itemCount: strings != null ? strings.length : 0, // <- stringsは非同期のため初期値がnull
itemBuilder: (BuildContext context, int index) => ListTile(
title: Text(strings[index]),
),
),
),
],
),
);
},
),
);
}
}
動作画面
おわりに
providerを活用することでStatelessWidgetのみでアプリを構築することも可能になると思います。