Custom ExpandetWidget by Riverbod Architecture

こんにちはEveryDaySoft代表の永田です。

今回は題名のカスタム化する必要がある場合の要件での対応を共有いたします。

アプリの個性、オリジナルを引き立たせたいという想いから、

デファクトスタンダードの作りではない場合が、案件ではよくあります。

開発者としては、その要望に対して価値という観点から作成したいと考えます。

今回紹介する挙動は、Exampleコードです。

Flutterでは拡張開閉するListの挙動に対して、矢印のデザインの位置調整を設定する機能はデフォルトでは存在しません。

矢印のデザインの位置調整ためだけに、packageを使用しますか?

矢印のデザインの位置調整ためだけのpackageはありませんでした。

なので、デフォルトのコードを改造するアプローチがベターな選択だと思います。

Exampleの挙動

矢印の設定を使用側がwidgetを設定しています。

Exampleのソースコード

リンク先のこの箇所で設定しています。


 

FlutterでのHookWidgetConsumerWidgetの違いは、基本的にそれぞれどのようにProviderからのデータにアクセスするかに関連しています。

まず、ConsumerWidgetについて説明します。ConsumerWidgetは、RiverpodのProviderからデータを読み取るためのウィジェットです。ConsumerWidgetはRiverpodに依存する任意のウィジェットを作成できます。ConsumerWidgetを使うと、ビルドメソッド内で直接watch関数を使用してデータを読み取ることができます。

watchを使用する例です。全体のbuildを期待できます。

https://gist.github.com/DaisukeNagata/5cd75375dc33860073f2bb674965107e#file-main-dart-L95

readを使用する例です。指定したproviderの値、method内の値を変更できます。

https://gist.github.com/DaisukeNagata/5cd75375dc33860073f2bb674965107e#file-main-dart-L116

ConsumerWidgetは watchは全体的なbuild、readは指定した値をbuildと思ってもらって良いかもしれません。

 

HookWidgetはRiverpodとflutter_hooksパッケージを組み合わせて使用します。

Flutter Hooksは、ウィジェットのライフサイクル内でリアクティブな値を管理するためのパッケージです。HookWidgetでは、useProviderフックを通じてProviderからデータにアクセスします。

https://gist.github.com/DaisukeNagata/5cd75375dc33860073f2bb674965107e#file-main-dart-L138

 

HookWidgetの useState、このフックは、コンポーネント内での状態を管理します。

useStateは値を格納し、その値が変更されるたびにウィジェットが再レンダリングされます。

HookWidgetの useEffect、 このフックは、外部のAPIからのデータの取得、タイマーの設定など を管理します。

useEffectはウィジェットのライフサイクルに連動して動作します。

HookWidgetの useProvider、 このフックは、RiverpodのProviderからデータを読み取るために使用されます。

ウィジェットが特定のProviderに依存しているときにこのフックを使用します。

初回のソースコードで矢印が変わらなかったのは2つ理由があります。

1.アニメーションが終了する前に、widget.gestureTapCallback();が呼び出され、フラグが変更していました。

https://gist.github.com/DaisukeNagata/5cd75375dc33860073f2bb674965107e#file-main-dart-L356

アニメーションが終わった頃にはフラグが変更し、buildが終了している状態です。挙動に変更が発生しません。

2. HookWidget を使用したwidgetなので、ref.watchを使用しても値が変わるが、全体がリロードしなかった。

useStateを使用しないと、全体がリロードしないのです。

良い意味で明示的に閉鎖的です。

 

 対応したコード

ListをTapすると実行します。アニメーションが完了すると、gestureTapCallBackを実行します。

isLoadingがuseStateを使用しているので、valueに値を設定すると値が変更します。

itemNotifier.toggleIsExpanded(index);はiconを判定しているフラグを変更します。


この箇所が変更になり

        child: isLoading.value
            ? const CircularProgressIndicator()
            : ListView.builder(

アニメーション後に矢印の挙動が変更します。

HookWidgetでなくてもConsumerWidgetで使用できますが、Listを追加するときにリロードなどのUIを見せたい場合など、

HookWidgetのuseStateで明示的に操作するのが簡単です。

またConsumerWidget、HookWidgetを使用すると、構造的になりがちなので、そこまで構造化する必要がない場合は、

statefullwidgetを使用して、setstateを使用し全体をbuildして状態を変更しても良いと思います。

ケースバイケースです。

ちなみにこの事象の修正案はChatGpt4に細かく確認しても、正解の方法を導かなかったので、自分で考えました。

ChatGptは機能と機能が連携した機能に対して、開発者が期待する挙動を予想した修正コードなどは導かないようです。

ChatGptを使用する場合は、条件を具体的に記載すれば、確実かもしれません。

 

以上、貴重なお時間お読みくださいまして、誠にありがとうございます。