🥲

私のProviderScopeの理解が間違っていた話

2024/03/09に公開

tl;dr

ProviderScope は、"スコープ"を作ります。
おそらく、外部からそのスコープ内に依存したProviderは取得できないと思います。

動作確認用DartPad
https://dartpad.dev/?id=6eecc76dcd7a7b7395425280d1275662

やりたかったこと

ProviderScopeを利用して、特定のIDやtag文字列(キー情報)を注入するWidgetを作成した。
任意のWidgetをそれで包むことで、その内部ではキー情報を取り回す必要がなくなった。

class _TagWrapper extends ConsumerWidget {
  const _TagWrapper({
    required this.tag,
    required this.child,
  });

  final String tag;
  final Widget child;

  
  Widget build(BuildContext context, WidgetRef ref) {
    return ProviderScope(
      overrides: [
        tagProvider.overrideWithValue(tag),
      ],
      child: child,
    );
  }
}

// 利用時
_TagWrapper(
  tag: 'tag1',
  child: _Counter(), //<- このWidgetの中では、tagがrefから取得できる。
),

これを利用して、そのキー情報に依存する状態も取得できるようになった。

final counterProvider = StateProvider<int>(
  (ref) {
    final tag = ref.watch(tagProvider);
    // some process uses tag.
    return 0;
  },
  dependencies: [tagProvider],
);

同じようなことが .family でも実現できるが、 ref.watch(counterProvider(tag)) のように毎回 tag を指定しなければならなく、冗長に感じたため。

期待した動作

2つの ProviderScope があるが、同一のtag文字列でoverrideしている。
同一の情報によってProviderが取得されるため、単一のインスタンスが取得できると思った。

(青枠のWidgetでインクリメントした値が、上部の赤枠部分の値にも反映されると期待した)

実際の動作

別々のインスタンスが返却されるため、値の同期はされない。

より具体的な話

上記の例では、1つのWidget内で完結していたが、実際に遭遇したのは以下のケース。

  • 1つのページ全体を ProviderScope で囲み、「このページで利用するキー情報はこれ」という感じで、その子Widgetではキー情報をかんたんに取得できるようにしていた。
    • アプリ内でPicture in Pictureのようにページを重ねて表示するケースがあり、「アプリ全体として、現在のキー情報はこれ」という指定ができなかった(これができれば、 StateProvider 的にもたせることもできた)
  • ページから、 showModalBottomSheet を利用して、その表示設定を変更できる機能が存在した。
  • showModalBottomSheetbuilder 内は、別のWidget treeとなるため、同じ値でoverrideする ProviderScope で囲むことで、ページ内の情報(ViewModel的なもの)を操作しようとした。
  • 結果、 builder 内のWidgetでは、新規のページ情報が取得されてしまい、下のページの情報が更新できなかった。

今回のケースでは、ボトムシート内では単純な参照だけできればよかったので、ページ側の refshowModalBottomSheet.builder の中に渡してしまった。

// 受け渡しイメージ
void _showMySheet() {
  showModalBottomSheet(
    builder: (BuildContext context) {
      return MySheet(
...
        pageRef: ref, // <- ADD
      ),

// 利用イメージ
  Widget build(BuildContext context, WidgetRef ref) {
    final viewModel = pageRef.read(myPageProvider);
  }

あんまり良くない気はしている。。。

Discussion