BLoCを意識しなくてもなんとかなるFlutter

# はじめに

Flutterを始めるとBLoC（BusinessLogicComponent）パターンという言葉を良く見聞きして使わなきゃって強迫観念に駆られて、実際に適用しようとすると`Stream`やら`InheritedWidget`が出てきて「うげげっ」って感じだったりします。

そこで[providerパッケージ](https://pub.dev/packages/provider)を利用すれば、その辺を上手に隠匿してくれてBLoCを意識せず利用することができたりします。

# providerの使い方

ファイル分割すると分かりづらくなりそうなので、一括で載せちゃいます。
詳細はコードのコメントを参照。

```dart
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]),
                    ),
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}
```

# 動作画面
最初の画面
![Screenshot_1588811701.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94252/5a92ceac-0d1b-8842-bc80-44e8b0ac2c27.png)

![Screenshot_1588813270.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94252/96bd4916-3062-8d58-e9d7-3c70aba56bbf.png)

次の画面
![Screenshot_1588811708.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94252/60e21cc3-8161-851c-29e5-fa7973d2f054.png)

# おわりに
providerを活用することでStatelessWidgetのみでアプリを構築することも可能になると思います。