見出し画像

Flutterアプリケーション改善の歩み 〜BLoCからstate_notifier+riverpodへ〜

この記事は、Showcase Gig Advent Calendar 2021 5日目の記事です。

こんにちは。Showcase Gigのtarossです。

昨年の12月、Showcase Gigにモバイルアプリケーションエンジニアとして入社して、ちょうど1年が経とうとしています。

Showcase Gigでは、いくつかのプロダクトに対して横断的に開発を進めるようにそれぞれチームが編成されています。

私の所属しているチームでは、店内飲食にフォーカスしたプロダクト『O:der Table』を軸に、店舗内で使用するアプリケーション『FloorManager』や管理ツールなども提供しています。

このFloorManagerはFlutterで開発されており、入社後は主にFloorManagerの開発を担当していました。

アドベントカレンダー5日目となる本記事は、FloorManagerの開発を通して感じた課題のひとつと、その改善の道のりについて少しお話します。

FloorManagerにおける状態管理手法

Flutterは比較的若い技術で、状態管理の手法についてもここ数年のうちにいくつかの移り変わりがあります。

FloorManagerは数年前から開発が進められており、当時モダンとされていたBLoC(Business Logic Component)パターンを用いて設計されていました。

BLoCパターンの詳細については、以下のDart Confの動画や、解説記事等を参照してください。

BLoCによる課題

詳細はほかの解説記事等に譲りますが、BLoCは特定のフレームワークに依存せず実現できるシンプルなルールのみが定義されている、という点が特徴になります。

BLoCのインターフェースは Stream/Sink でのみ構成され、BLoCの依存は注入可能であること。また、プラットフォームに関する知識を持たないこと。これらを満たしていれば、実装手段については問わない。というものです。

シンプルで容易なルールではありますが、そのシンプルさ、またリアクティブプログラミング自体の難しさなどによって維持されにくい傾向にあると感じています。 FloorManagerは私が担当するまでに複数の人の手を渡ってきており、この時点でいくつかの問題を抱えていました。

  • BLoCのインターフェースにvoidメソッドが生えており、メソッド内で処理されたデータが内部のSubjectに流し込まれている(Streamは処理後のデータのリアルタイム伝搬にのみ使われている)

  • ひとつのBLoC内にいくつものSubjectが生えており、そのいくつかは重複するデータを取り扱う

  • BLoC間でデータをやりとりするために、アプリケーション全体の状態を管理するBLoCが生まれている

これらにより、新規機能や改善の速度が思ったように出ないという課題がありました。

FloorManager担当後は(機能追加や改修作業の合間に)BLoCを用いている既存のコードが提供している価値を阻害せず、かつ迅速にプロダクトの要望に応えられるように、よりよい形に改善する取り組みを行っていました。

モダンなFlutterの状態管理を実現する

BLoCパターンがしばらく話題となったのちは、状態を伝搬するためのしくみとしてInherited WidgetのラッパとなるproviderがGoogle推奨になったり、そのproviderが抱えていた課題を解決するためにriverpodが開発されるなどしています。

また、不変性を保った状態管理を実現する際に便利なパッケージにはstate_notifierfreezedなどが開発されています。

前述の課題を解決するために、これらのパッケージの力を借りて既存のコードを逐次分割・整理していくことを目標としました。

BLoCからStateNotifierへ

何事も段階を踏んで取り組むことが重要です。

FloorManager以外の開発タスクや、FloorManagerの機能開発のタスクと並行して進める必要があります。また、Flutterのバージョンも古いままだっため、これを最新化する対応も並行して行うことにしました。1.22.4からのアップグレードとなったため、Sound Null Safety対応も行いました。いくつかのパッケージはSound Null Safetyに対応しておらず、このタイミングで乗り換える必要もありました。

これらの作業の合間に移行を進めるために、state_notifier + freezed + providerを使った形に置き換える、という地点を改善の第一段階として設定しました。 現在はriverpodというproviderの発展系が並行して提供されていますが、BLoCをStateNotifierに置き換えるところを乗り越えれば、provider -> riverpodの移行コスト自体は大きくないと判断しています。

現時点で、各機能BLoCや全体管理用のBLoCはすべてStateNotifierに置き換えられています。この置き換えに合わせて、少しずつ肥大化していたBLoCのいくつかは2つ以上のStateNotifierに分割して実装し直しています。StateNotifierはfreezedで定義された不変な状態を持ち替えてゆく、という形になっており、新しいFloorManagerに向けての改善は一段落しつつあります。

終わりに

現時点でFloorManagerはstate_notifier + freezed + providerを使った状態管理に移り変わりつつありますが、状態がWidgetツリーのコンテキストを経由することに由来する課題も見つかっています(これはもともとriverpodの開発動機に挙げられていたもので、予見されていたものではあります)。

第一段階の改善を踏まえ、今までに置き換えたStateNotifier群を軸にして、riverpodによるよりシンプルなStateNotifier間の連携が実現できるように改善を進めていく予定です。

次回は(も)第4回で登場した林さんにバトンを返します。第4回から引き続き、どんな記事になるのか楽しみにしています。

この記事が気に入ったらサポートをしてみませんか?