torikatsu.dev

Flutterとかプログラミングとかガジェットとか書きます

【Flutter】riverpod 1.0.0-dev.0の変更点まとめ

こんにちは、とりかつ((@torikatsu923)https://twitter.com/torikatsu923)です。

今朝、riverpodの1.0.0-dev.0が公開されました!

以前記事で触れましたが、4月の半ばごろにriverpodの作者の方からriverpodに破壊的変更が入るかもしれないというアナウンスがありました。 torikatsu923.hatenablog.com

Changelogを確認したところ、今回のアップデートはそのアナウンスを受けてのものと考えて良さそうです。 個人的に欲しかった機能がつまっているアップデートなので、使うのがとても楽しみです!

今回のアップデートは変更点が20個程含まれており、破壊的変更も多く含まれています。 pub.dev

そこで今回の記事ではriverpodの変更点についてまとめていきます。

アップデートの方針の考察

今回のアップデートのキーは「Providerへアクセスする方法を統一する」だと思われます。 初めてriverpodを触る時、Providerをreadする方法の複雑さにウッとなる方も多いと思います。 https://riverpod.dev/assets/images/reading-eddc30e98d777456a442ddd5b181eb2a.svg

以前の作者さんの(アナウンス)https://github.com/rrousselGit/river_pod/issues/335や、change logでproviderへのアクセス周りで破壊的変更が多く入っていることから、 アップデートにはProviderへアクセスする方法を統一し、よりシンプルにするにする目的があると考えられます。

実際に変更後はref.read, ref.watch, ref.listenによってProviderへのアクセス、UIでの状態の監視、状態変更の監視が可能になっています。

以下ではこの点を踏まえつつ、変更点を追っていきます。

変更点一覧

useProviderの削除

HookWidgetのuseProviderがなくなり、Widget refを使用してProviderへアクセスするように変更が入っています。 HookConsumerWidgetを継承することで、buildの引数にWidget refを受け取ることができるようになります。

Before

class Example extends HookWidget {
  @override
  Widget build(BuildContext context) {
    useState(...);
    int count = useProvider(counterProvider);
    ...
  }
}

After

class Example extends HookConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    useState(...);
    int count = ref.watch(counterProvider);
    ...
  }
}

pub devより引用

ConsumerWidgetのbuildメソッドでWidget refを受け取るようにする変更

従来は以下のようにbuildメソッドでScopedReaderを受け取っていましたが、変更後はWidgetRefを受け取るようになりました。 変更後はProviderの変更をwatchしたい際はref.watchとする必要があります。

Before

class A extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final state = watch(someProvider);
  }
}

After

class A extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(someProvider);
  }
}

ProviderListenerを非推奨にする変更

WidgetReflistenという状態の変更を監視する関数ができました。その関係で、今まで状態の変更を監視する際に使用されていたProviderListenerが非推奨になりました。

この変更により、今までWidgetをネストしなければならなかったところをとてもシンプルに記述することができるようになりました!

Before

class A extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ProviderListener(
      provider: someProvider,
      onChange: (context, state, child) { ... },
      child: Container( ... ),
    );
  }
}

After

class A extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    ref.listen(someProvider, (state) {
       // something
    }
  }
}

ConsumerStatefulWidget, ConsumerStateの追加

StatefulWidgetとConsumerWidgetが合体したConsumerStatefulWidgetが追加されました。 StatefulWidgetのライフサイクルにフックしてProviderの処理を走らせたいが、状態の管理はProviderに行わせたいみたいな場合、いままではStatefulWidgetでWidgetを定義して、build内でConsumerで括ってあげる必要がありました。 今回のConsumerStatefulWidgetの追加で、Consumerで括る必要がなくなったため、よりシンプルに記述できるようになりました。

watchでselectでstateの特定の値の変更のみ検知できるようにする変更

userのフィールドにusernamestatusがあるとします。 以下のコードではstatusに変更があった場合はリビルドされず、usernameに変更があった場合にリビルドされるようになりました。

final userProvider = StateNotifierProvider<UserController, User>(...);

Consumer(
  builder: (context, ref, _) {
    bool userName = ref.watch(userProvider.select((user) => user.name));
  },
)

グローバルな状態とリビルドについて悩んでいたため、この変更は個人的にとても嬉しいです!

overrideWithValueの削除

今まで.familyで作成したProviderはoverrideする必要がありました。その際値のみをオーバーライドするoverrideWithValueとProviderごとオーバーライドするoverrideWithProviderの2種類の方法がありました。 しかし、今回の変更でoverrideWithProviderに一本化されたようです。

ProviderObserverが変更前と変更後の2種類の状態を受け取れるようにする変更

ProviderObserverが変更の前後の状態を受け取れるように変更が入りました。 これによって状態の差分をみたりする処理が書きやすくなりました。

ProviderObserver.mayHaveChangedの削除

ProviderObserver.mayHaveChangedが削除されました。 これは破壊的変更ですが、個人的にはあまり使っていない機能だったため影響はありませんでした。

ref.listenの追加

先で触れたようにref.listenが追加されました。 バージョンアップ後はダイアログの表示等はこれを使用することになります。

ProviderReferenceを非推奨にする変更

ProviderReferenceが非推奨になり、代わりにProviderBaseが採用されました

全てのProviderがProviderRefBaseをパラメータとして受け取るようにする変更

今までProviderは以下のようにrefを受け取っていましたが、これがProviderRefBaseに変更されました。

Provider((ref) => ...);

この変更でrefがstateというプロパティを持つようになり、ref.state++のようにすることで状態を変更することができるようになりました。 また、StateProviderのrefにはcontrollerというプロパティが追加され、ref.controller....でStateControllerにアクセスできるようになりました。

ProviderReference.mountedの削除

ProviderReference.mountedが削除されました。代わりにonDisposeを使用することで似たような挙動を実現できるようです。

Provider<T>((ref) {
  var mounted = true;
  ref.onDispose(() => mounted = false);
});

ProviderContainerのdebugproviderValuesとdebugProviderElementsの削除

ProviderContainerのdebugProviderValuesとdebugProviderElementsが削除され、 ProviderContainer.getAllProviderElementsを使用できるようになりました

StreamProviderのlast, stream, futureがプロバイダーの再構築の回数に依存しないようにする変更

関連するProviderの再構築の回数に関係ないfuture, streamを外部に公開するようになりました。

Providerの値が同じ時、Providerをオーバーライドできるようにする変更

StreamProvider<User>があった時、Provider<AsyncValue<User>>でオーバーライドすることができるようになりました。

ref.refreshの呼び方変更

ref.refreshの呼び方がref.container.refreshに変更されました。

ref.onDisposeのタイミング変更

Providerの依存関係の変更がわかったらすぐにonDisposeが呼ばれるようになりました。

Providerの状態の再計算のタイミング変更

Providerの依存関係が変更されてリスナがいるとき、次の読み取りまで状態の再計算を待たなくなりました。

ProviderContainer.pumpの追加

ProviderContainer.pumを使うことで、awaitのように簡単にproviderが状態の変更を通知するまで待つことができるようになりました。

familyとdisposeを併用した時に状態が残る問題の修正

familyとdisposeを一緒に使用した際状態が残ってしまう問題がありましたが、今回のアップデートで解決されたようです。

おわりに

今回の記事ではriverpodの変更点を追っていきました。 Providerへのアクセスする際の構文がrefを経由して行われるように統一されたので、より使いやすくなったと感じています! またいくつかの変更によってシンプルに処理をかけるようになったのもとても嬉しいです!

それではよい開発ライフを!