🤔

riverpodとproviderは共存してもいい理由を考える

2024/06/09に公開

Flutterにおける状態管理のライブラリで、おそらくいま最も使われているであろうriverpod。状態管理からDIまで、様々な用途に使用することができます。

ところで、RiverpodはProviderの後継のライブラリとされています。いるのですが、ドキュメントにはこのように記載があります。

Riverpod and Provider can coexist
Keep in mind that it is entirely possible to use both Provider and Riverpod at the same time.
Indeed, using import aliases, it is possible to use the two APIs altogether.
This is also great for readability and it removes any ambiguous API usage.
https://riverpod.dev/ja/docs/from_provider/quickstart

riverpodとproviderは共存できる、というものです。これは単に後方互換性がある、ということだけを示すというわけではないと考えています。

riverpodで表現できないパターンを考える

たとえば、TabViewに異なる値を持つ同じクラスを持つ場合を考えてみましょう。下記は概念的な例で、同じクラスの異なる値をTabBarViewで表示するものです。

class Tabs extends ConsumerWidget {
    
    Widget build(BuildContext context) {
        final list = ref.watch(somethingNotifierProvider
            .select((value) => value.list),
        );
        return TabBarView(
            children: [
                for(final element in list)
                    TabContent(list),
            ],
        );
    }
}

class TabContent extends StatelessWidget {
    final TabContentData data;
    const TabContent({required this.data});

    
    Widget build(BuildContext context) {
        return Text(data.something);
    }
}

TabContentの中身がどんどんネストしていくと、TabContentDataの中身をウィジェットの引数間でリレーを行わないといけなくなります。

riverpodでfamilyを使うにしても、たとえばそのキー(これが何個目かなどといったもの)を同様にネストしなければなりません。

これはTabBarViewの場合ですが、複雑なダイアログの場合や、同じ画面を遷移上何度もスタックに来るような場面でも起きる可能性があるでしょう。こうした場合、riverpodでなんとかしようとするより、providerを併用した方がいい可能性があります。

providerを併用する

providerを併用する場合、下記のようになるでしょうか。

class Tabs extends ConsumerWidget {
    
    Widget build(BuildContext context) {
        final list = ref.watch(somethingNotifierProvider
            .select((value) => value.list),
        );
        return TabBarView(
            children: [
                for(final element in list)
                                       provider.Provider.value(
                        value: list,
                        child: TabContent(),
                    ),
            ],
        );
    }
}

class TabContent extends StatelessWidget {
    const TabContent();

    
    Widget build(BuildContext context) {
        return Text(context.read<TabContentData>().something);
    }
}

これでTabContentウィジェットは引数を受け取らなくて済むようになり、さらにこの下位のウィジェットも引数のリレーを行わなくて済みます。

riverpodとproviderのそれぞれの役割をかんがえる

riverpodがNotifierで状態を管理したり、DIの役割などを行ってくれます。一方でこうした「ウィジェットツリーに依存した状態」を表現しようとすると、riverpod自身がそこから脱却するというコンセプトであるがゆえに、かえって煩雑になることがあります。

このようなウィジェットツリーがキーになるような状態の場合、「ウィジェットツリーにむしろ依存した設計になっている」providerを活用するのはいかがでしょうか。

もちろん、ウィジェットツリーに依存するということはビルド時には分からず実行時にしか起きないようなエラーが発生しうる、providerもひとつの型に対して一つの状態しか持てないなど、その使う場面や使い方はよく留意しないといけません。しかしながら、それらのデメリットよりも大量の引数のリレーをしなくていいというメリットを上回るのであれば、検討する余地があるかと思います。

Discussion