1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

はじめに

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

動作画面

最初の画面
Screenshot_1588811701.png

Screenshot_1588813270.png

次の画面
Screenshot_1588811708.png

おわりに

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?