SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Flutterで始めるモバイルアプリ開発

【Flutter解説】データをもとに画面処理を行う方法──ビジネスロジックと画面処理のアーキテクチャを知ろう

Flutterで始めるモバイルアプリ開発 第24回

BLoCパターン(ProviderとStreamController/StreamBuilder)を用いる方法

 続いて、BLoCパターンに基づいた実装方法の一例を紹介します。BLoCとは、Business Logic Componentの略になります。BLoCとはここでは深く説明はしませんが、FlutterにおいてはStreamを用いてビジネスロジックと画面間のデータ連携を行う手法になります。

全体の構造

 BLoCパターンを用いた場合の構造について示したのが図4です。

 前回の変更(イベント)通知と大きく違うポイントは、画面処理とロジック以外にデータ処理のレイヤーが追加されたことです。この処理が追加されたことでデータのみを定義する構造クラス(DateModel)が必要になります。そして、そのデータがStreamを通じてビジネスロジックから画面へ提供される仕組みになります。

図4:BLoCパターンを用いた場合の構造
図4:BLoCパターンを用いた場合の構造

 今回のような単純なサンプルでは変更(イベント)通知との違いがよくわかりにくいですが、データ処理レイヤーを別途もうけることで以下のようなメリットも生じます。

  1. 1つの処理から異なるデータ形式に分割して変更通知を伝えることができる。
  2. 複数の処理からデータを統合をしてから、画面ウィジェットに変更通知を伝えることができる。
  3. 変更のタイミングと画面表示のタイミングを実装者が意識する必要がない。

 (1)(2)は複数のStreamを利用することでデータ処理レイヤーだけで対応できます。例えば、処理が共通でも必要なデータ形式が違う画面がある場合などに有用です。

 また、(3)は少々わかりにくいですが、先ほどのイベント処理でnotifyListeners()を現在の画面Frame処理の後に予約するような問題回避をしましたが、そのようなことを考える必要性がなくなります。なぜなら、処理タイミングと画面更新のタイミングが同期していなくてもStreamによって矛盾がないデータが保証されているためです。

コード例

 先ほどの構造を実装したコード例がリスト4になります。コード自体はイベント通知と比べて大きな違いがないと思います。

[リスト4]BLoCパターンを使った場合の実装例(lib/StreamScreen.dartの抜粋)
// (1) データ定義
class DateModel {
  final List<DateItem> items;
  final bool loading;
  DateModel(this.items, this.loading);
}
// (2) ロジック定義
class WeekBloc{
  // (3) Streamの準備
  final _controller = StreamController<DateModel>();
  Stream<DateModel> get stream => _controller.stream;
  List<DateItem> _last = [];

  void load() async{
    // (4) データの追加(ローディング中)
    _controller.sink.add(DateModel(_last, true));
    var res = await http.get(Uri.parse("http://192.168.1.1/list.json"),headers: {
      'Accept' : 'application/json'
    });
    var json = jsonDecode(utf8.decode(res.bodyBytes));
    var week = json['items'].map<DateItem>((e) => DateItem.fromJson(e)).toList();
    _last = week;
    // (5) データの追加(データ取得)
    _controller.sink.add(DateModel(week, false));
  }
  void dispose(){
    _controller.close();
  }
}

class StreamScreen extends StatelessWidget {
  const StreamScreen({super.key});
  @override
  Widget build(BuildContext context) {
    // (6) Streamを提供する親ウィジェット
    return Provider<WeekBloc>(
        create: (context) => WeekBloc(),
        dispose: (_,bloc) => bloc.dispose(),
        child: const DateListScreen(),
    );
  }
}

class _DateListScreen extends State<DateListScreen>{

  @override
  void initState() {
    super.initState();
    final bloc = Provider.of<WeekBloc>(context, listen: false);
    bloc.load();
  }

  @override
  Widget build(BuildContext context) {

    final bloc = Provider.of<WeekBloc>(context);
    // (7) 画面処理
    return StreamBuilder<DateModel>(
      stream: bloc.stream,
      initialData: null,
      builder: (context, snapshot) {
        final DateModel? block = snapshot.data;

        return Scaffold(
          appBar: AppBar(
            //: (省略)
          ),
          body: Stack(
            children: [
              // : (省略)
            ],
          )
        );
      },
    );
  }
  // : (省略)
}

 (1)はStream上に流れるデータの定義です。そして、ビジネスロジックを定義したのが(2)で、Streamをあつかうため(3)StreamControllerを用意します。load()メソッドが実際のデータを取得する処理で有ることが先ほどと変わりませんが、ローディング中を示すデータを(4)のように追加します。ここで_lastという変数を用意している理由は、リロード処理において前回データが必要なためです。

 また、データ取得後でも再度(5)のようにStreamにデータを追加します。(6)のProviderにてビジネスロジックのインスタンスを管理します。そして、(7)のStreamBuilderがデータを受け取る画面ウィジェットになります。

最後に

 アーキテクチャを採用する際に1つの方法に統一しようと思う方もいるかも知れません。しかし、今回紹介した3つのパターンでもわかるように、実際には違う原理で実現していることが分かると思います。

 実際に開発するアプリでは1つの方法だけでは問題を無理に回避するための分かりにくいコードができたり、必要以上に複雑さを生じさせているケースをよく見ます。そのため、無理に統一する必要はなく、いくつからパターンから採用できる方がよいと筆者は考えます。

 また、今回紹介できませんでしたが、Reactなどで普及しているReduxパターンというのもあるので、興味がある方はいろいろと調べてみるとよいと思います。

 次回はFlutter特有のことについていくつか紹介します。例えば、今回のコードでもBuildContextを通じてインスタンスの連携などを行いましたが、その辺りにもふれていきたいと思います。

参考資料

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
Flutterで始めるモバイルアプリ開発連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 小林 昌弘(コバヤシ マサヒロ)

WINGSプロジェクト について> 有限会社 WINGSプロジェクト が運営する、テクニカル執筆コミュニティ(代表 ...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/18105 2023/08/21 11:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー