👀

Provider の Consumer と Riverpod の Consumer は別モノなので2021/01時点のコードを追いかけてみた

2021/01/25に公開

個人的に混同してしまっていたのでメモ

TL;DR

Provider の Consumer<T> と Riverpod の Consumer は別モノだった

勝手に勘違いしてただけなのですが
Provider の Consumer<T> と Riverpod の Consumer は別モノでした。

Provider から Riverpod への乗り換え解説系の記事を色々と読んでいて
hooks_riverpod を使わず flutter_riverpod を使う場合は
Consumer.builderwatch で Provider の変更を監視する、という方法を
なんの疑いもなく Provider の Consumer<T> と同様の物だという先入観で考えて
理解に苦しんでいたのですが、
APIリファレンスをみたところ全くの別モノだったので「あぁ、まぁそりゃそうか」となりました。
せっかくなのでコードの方も追いかけてみましょう。

Provider の Consumer<T>

https://github.com/rrousselGit/provider/blob/a86600a011cf3fa728a4328354c8247d68295949/lib/src/consumer.dart#L157

...
class Consumer<T> extends SingleChildStatelessWidget {
  /// {@template provider.consumer.constructor}
  /// Consumes a [Provider<T>]
  /// {@endtemplate}
  Consumer({
    Key key,
     this.builder,
    Widget child,
  })  : assert(builder != null),
        super(key: key, child: child);

  /// {@template provider.consumer.builder}
  /// Build a widget tree based on the value from a [Provider<T>].
  ///
  /// Must not be `null`.
  /// {@endtemplate}
  final Widget Function(BuildContext context, T value, Widget child) builder;

  
  Widget buildWithChild(BuildContext context, Widget child) {
    return builder(
      context,
      Provider.of<T>(context),
      child,
    );
  }
}
...

nested パッケージの SingleChildStatelessWidget を継承したクラスですね。
キモは Provider.of<T>(context) の部分で、
このおかげで値の変更に応じて処理されるんですね。

Riverpod の Consumer

https://github.com/rrousselGit/river_pod/blob/4f5e37989f61b0e308946753f4bf27aa781af41d/packages/flutter_riverpod/lib/src/consumer.dart#L132

...
typedef ConsumerBuilder = Widget Function(
  BuildContext context,
  ScopedReader watch,
  Widget child,
);
...

class Consumer extends ConsumerWidget {
  /// {@template riverpod.consumer}
  const Consumer({
    Key key,
     ConsumerBuilder builder,
    Widget child,
  })  : _child = child,
        _builder = builder,
        assert(builder != null, 'the parameter builder cannot be null'),
        super(key: key);

  final ConsumerBuilder _builder;
  final Widget _child;

  
  Widget build(BuildContext context, ScopedReader watch) {
    return _builder(context, watch, _child);
  }
}
...
abstract class ConsumerWidget extends StatefulWidget {
  /// {@macro riverpod.consumerwidget}
  const ConsumerWidget({Key key}) : super(key: key);
  Widget build(BuildContext context, ScopedReader watch);

  
  _ConsumerState createState() => _ConsumerState();
}
...

なんと! Riverpod の ConsumerStatefulWidget を継承した ConsumerWidget を継承したものでした。この時点で全くチガウモノですね。

ScopedReader の定義はこちらです
river_pod/packages/riverpod/lib/src/framework/scoped_provider.dart | GitHub
https://github.com/rrousselGit/river_pod/blob/master/packages/riverpod/lib/src/framework/scoped_provider.dart

Riverpod の公式サンプルでは Consumer を使わずに
ConsumerWidget をそのまま使っているサンプルも上がっていますし、
hooks_riverpod の方でも HooksWidget を使うサンプルが推奨されている(?)ようですし
ConsumerWidget を直接使って欲しいのかな……? とも考えられました。考えすぎかもしれませんが。

まとめ

というわけで皆さんは混同しないように注意してください。
といっても、混同してしまっていたのは私だけかもしれませんね。

参考

Discussion