RiverpodのautoDispose深掘り
Riverpod、便利ですよね。毎日使っています。
そんなRiverpodには、.autoDispose
という便利な機能があります。
この.autoDispose
は、効率的なアプリケーション開発を助けてくれます。
この仕組みについて、コードから理解を深めてみよう、というのが記事の趣旨です。
なお、この文章はriverpod: 2.3.1を参照しています。
ProviderContainer
Riverpodと手始めに、RiverpodにおけるProviderContainer
の破棄タイミングについて確認しましょう。なお、ここではFlutterにRiverpodを利用したケースを紹介します。
確認するべきクラスは次のとおりです。
クラス | パッケージ | 継承 |
---|---|---|
ProviderScope | flutter_riverpod | extends StatefulWidget |
ProviderScopeState | flutter_riverpod | extends State<ProviderScope> |
ProviderContainer | riverpod | implements Node |
ProviderScope
RiverpodをFlutterのアプリケーションに導入するには、ProviderScope
を利用します。大抵の場合は、サンプルコードにあるように、runApp
の中で呼び出す形になるでしょう。
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
ProviderScope
はStatefulWidget
を継承しているため、これはMyApp
の親に1つStatefulWidget
を置いています。runApp
の直下は、アプリケーションのrootとなるWidgetです。このため、起動から破棄までを管理できることになります。
ProviderScope
はStatefulWidget
なため、固有のState
を持っています。それがProviderScopeState
です。そして、このProviderScopeState
のプロパティとして、ProviderContainer
を保持しています。
下にコードを抜粋していますが、お時間があれば、ぜひ一度ソースコードを眺めてみてください。
class ProviderScopeState extends State<ProviderScope> {
late final ProviderContainer container;
void initState() {
super.initState();
final parent = _getParent();
container = ProviderContainer(
parent: parent,
overrides: widget.overrides,
observers: widget.observers,
);
}
Widget build(BuildContext context) {
return UncontrolledProviderScope(
container: container,
child: widget.child,
);
}
void dispose() {
container.dispose();
super.dispose();
}
}
ここで登場するUncontrolledProviderScope
はInheritedWidget
を継承したクラスで、この要素に対してProviderContainer
を提供する役割を持っています。実装を確認すると細々とした調整を行っているようなのですが、今回はスキップします。
ProviderScope
により、アプリケーションの起動時に用意され破棄時にdispose
されるProviderContainer
が提供されることがわかりました。では、このProviderContainer
をどのようにWidgetが呼び出しているのかを、先に確認します。
ConsumerStatefulWidget
RiverpodとRiverpodをFlutterで利用するためには、Consumer
・ConsumerWidget
・ConsumerStatefulWidget
のいずれかを利用します。
クラス | パッケージ | 継承 |
---|---|---|
ConsumerWidget | flutter_riverpod | extends ConsumerStatefulWidget |
ConsumerStatefulWidget | flutter_riverpod | extends StatefulWidget |
ConsumerStatefulElement | flutter_riverpod | extends StatefulElement implements WidgetRef |
ProviderContainer | riverpod | implements Node |
ConsumerWidget
Consumer
はConsumerWidget
を継承し、builderによる子要素の生成を提供するクラスです。コードを確認すると簡単にわかるのですが、Consumer
とConsumerWidget
の間にはほとんど差がありません。StatefulWidgegt
の内部でProviderを参照したいケースや、child
プロパティを利用したパフォーマンスの最適化を行う場合、ConsumerWidget
ではなくConsumer
を使うべきとされています。
ConsumerWidget
はConsumerStatefulWidget
を継承したクラスです。実態としてはStatefulWidget
の継承クラスになりますが、API的にStatelessWidget
のように利用できるよう、調整されています。
APIの関係性を把握するため、必要な箇所だけ抜粋すると、次のような処理になっています。
abstract class ConsumerWidget extends ConsumerStatefulWidget {
const ConsumerWidget({super.key});
Widget build(BuildContext context, WidgetRef ref);
_ConsumerState createState() => _ConsumerState();
}
class _ConsumerState extends ConsumerState<ConsumerWidget> {
WidgetRef get ref => context as WidgetRef;
Widget build(BuildContext context) {
return widget.build(context, ref);
}
}
abstract class ConsumerState<T extends ConsumerStatefulWidget> extends State<T> {
late final WidgetRef ref = context as WidgetRef;
}
サッと見てわかる通り、重要な実装は全てConsumerStatefulWidget
にまとまっています。
ConsumerStatefulWidget
ConsumerStatefulWidget
はStatefulWidget
を継承したクラスです。State
としてConsumerState
を、そしてStatefulElement
としてConsumerStatefulElement
を返します。
このうち、Riverpod的に重要なのはConsumerStatefulElement
です。
下にコードを記載します。
処理に影響がない範囲でコードの省略や修正をしていますので、ぜひお時間のある時に元のコードをご確認ください。
class ConsumerStatefulElement extends StatefulElement implements WidgetRef {
ConsumerStatefulElement(ConsumerStatefulWidget super.widget);
late ProviderContainer _container = ProviderScope.containerOf(this);
var _dependencies = <ProviderListenable<Object?>, ProviderSubscription<Object?>>{};
Map<ProviderListenable<Object?>, ProviderSubscription<Object?>>? _oldDependencies;
Widget build() {
try {
_oldDependencies = _dependencies;
_dependencies = {};
return super.build();
} finally {
for (final dep in _oldDependencies!.values) {
dep.close();
}
_oldDependencies = null;
}
}
void unmount() {
super.unmount();
for (final dependency in _dependencies.values) {
dependency.close();
}
}
T watch<T>(ProviderListenable<T> target) {
return _dependencies.putIfAbsent(target, () {
final oldDependency = _oldDependencies?.remove(target);
if (oldDependency != null) {
return oldDependency;
}
return _container.listen<T>(
target,
(_, __) => markNeedsBuild(),
);
}).read() as T;
}
T read<T>(ProviderListenable<T> provider) {
return ProviderScope.containerOf(this, listen: false).read(provider);
}
State refresh<State>(Refreshable<State> provider) {
return ProviderScope.containerOf(this, listen: false).refresh(provider);
}
void invalidate(ProviderOrFamily provider) {
_container.invalidate(provider);
}
BuildContext get context => this;
}
ProviderScope.containerOf
が頻繁に呼び出されているのがわかりますね。これはcontextから参照できるInhertedWidget
を参照し、ProviderContainer
を取得する関数です。先ほど確認した通り、大抵の場合はアプリケーションのrootにProviderScope
を置いているため、アプリケーション全体で利用できるProviderContainer
を参照することになります。
StatefulElement
を継承しているクラスでは、_dependencies
(ref.watch
を呼び出したもの)と_listeners
(ref.listen
を呼び出したもの)の更新をしています。
Riverpodの動きを把握するためには、watch
とbuild
、そしてunmount
を確認すれば大丈夫です。
Widget build() {
try {
_oldDependencies = _dependencies;
_dependencies = {};
return super.build();
} finally {
for (final dep in _oldDependencies!.values) {
dep.close();
}
_oldDependencies = null;
}
}
void unmount() {
super.unmount();
for (final dependency in _dependencies.values) {
dependency.close();
}
}
T watch<T>(ProviderListenable<T> target) {
return _dependencies.putIfAbsent(target, () {
final oldDependency = _oldDependencies?.remove(target);
if (oldDependency != null) {
return oldDependency;
}
return _container.listen<T>(
target,
(_, __) => markNeedsBuild(),
);
}).read() as T;
}
build
の処理は一見わかりにくいのですが、ConsumerStatefulWidget
を継承したクラスでsuper.build
を呼び出すことはほぼないため、実行されることは稀です。unmount
はElementのライフサイクルに詳細があるのですが、非アクティブになってから所定の時間後に遷移する状態です。一度unmount
になったElement
は、再度Element
のtreeに追加されることがないため、破棄されることが決定ずれられたタイミングであると言えます。
ProviderContainer
のdispose
ここまでにみてきた処理は、基本的な呼び出しと破棄の処理です。
ProviderはProviderScope
内のProviderContainer
に紐づいています。最も基本的なConsumerStatefulWidget
のコードを見ると、read
とwatch
ともにProviderScope.containerOf
を利用した取得であることが確認できます。
またConsumerStatefulWidget
のunmount
時、ref.watch
で参照したProviderSubscription
がclose
されます。これはProviderContainer
のlisten
関数の呼び出しであり、stateの変更時に呼び出されるコールバックです。
Provider
Riverpodとここからは、Providerについて確認していきます。
ProviderにはProvider
の他に、FutureProvider
やNotifierProvider
などが存在します。今回は「とりあえず処理を追いたい」ので、Provider
を中心に確認します。
クラス | パッケージ | 継承 |
---|---|---|
Provider | riverpod | extends InternalProvider<T> with AlwaysAliveProviderBase<T> |
InternalProvider | riverpod | extends ProviderBase<T> with OverrideWithValueMixin<T> |
ProviderBase | riverpod | extends ProviderOrFamily with ProviderListenable<T> implements ProviderOverride, Refreshable<T> |
ProviderElementBase | riverpod | implements Ref<T>, Node |
AutoDisposeProvider | riverpod | extends InternalProvider<T> |
AutoDisposeProviderElement | riverpod | extends ProviderElement<T> with AutoDisposeProviderElementMixin<T> implements AutoDisposeProviderRef<T> |
AutoDisposeProviderElementMixin | riverpod | on ProviderElementBase<T> implements AutoDisposeRef<T> |
Provider
Provider
はいくつものクラスを継承、mixinされることで成り立っています。今回は、実装の中心となるProviderBase
を確認します。
ProviderBase
は一見すると読みにくいのですが、ref.read
とref.watch
で呼び出される関数を確認することで、割合簡単に把握できます。以下、把握用にコードを抜粋します。
abstract class ProviderBase<T> extends ProviderOrFamily with ProviderListenable<T> implements ProviderOverride, Refreshable<T> {
ProviderSubscription<T> addListener(
Node node,
void Function(T? previous, T next) listener, {
required void Function(Object error, StackTrace stackTrace)? onError,
required void Function()? onDependencyMayHaveChanged,
required bool fireImmediately,
}) {
onError ??= Zone.current.handleUncaughtError;
final element = node.readProviderElement(this);
element.flush();
if (fireImmediately) {
handleFireImmediately(
element.getState()!,
listener: listener,
onError: onError,
);
}
element._onListen();
return node._listenElement(
element,
listener: listener,
onError: onError,
);
}
State read(Node node) {
final element = node.readProviderElement(this);
element.flush();
// In case `read` was called on a provider that has no listener
element.mayNeedDispose();
return element.requireState;
}
}
ref.read
read
にはNode
が登場します。ProviderContainer
がNode
をimplemntしていることと、ConsumerStatefulWidget
でProviderScope.containerOf
を呼び出していたことを思い出せば、処理の流れは把握できますね。
class ConsumerStatefulElement extends StatefulElement implements WidgetRef {
T read<T>(ProviderListenable<T> provider) {
return ProviderScope.containerOf(this, listen: false).read(provider);
}
}
class ProviderScope extends StatefulWidget {
/// Read the current [ProviderContainer] for a [BuildContext].
static ProviderContainer containerOf(
BuildContext context, {
bool listen = true,
}) {
UncontrolledProviderScope? scope;
if (listen) {
scope = context //
.dependOnInheritedWidgetOfExactType<UncontrolledProviderScope>();
} else {
scope = context
.getElementForInheritedWidgetOfExactType<UncontrolledProviderScope>()
?.widget as UncontrolledProviderScope?;
}
if (scope == null) {
throw StateError('No ProviderScope found');
}
return scope.container;
}
}
class ProviderContainer implements Node {
Result read<Result>(
ProviderListenable<Result> provider,
) {
return provider.read(this);
}
}
abstract class ProviderBase<T> extends ProviderOrFamily
with ProviderListenable<T>
implements ProviderOverride, Refreshable<T> {
State read(Node node) {
final element = node.readProviderElement(this);
element.flush();
// In case `read` was called on a provider that has no listener
element.mayNeedDispose();
return element.requireState;
}
}
ProviderContainer
のread
が「引数として渡されたprovider
のread
関数を呼び出している」ため混乱するかもしれません。順を追って読んでいくと、Node
に対してProviderBase<T>
を渡していることが把握できます。
readProviderElement
の実装は、Node
をimplementしているProviderContainer
にあります。
class ProviderContainer implements Node {
ProviderElementBase<T> readProviderElement<T>(
ProviderBase<T> provider,
) {
if (_disposed) {
throw StateError(
'Tried to read a provider from a ProviderContainer that was already disposed',
);
}
final reader = _getStateReader(provider);
return reader.getElement() as ProviderElementBase<T>;
}
}
_getStateReader(provider)
の内容を短くまとめるのは難しいのですが、おおよそ「provider
に対応する_StateReader
を生成/キャッシュ」している関数だと言えます。そして_StateReader
はProviderElementBase
を生成/キャッシュするクラスです。
ProviderElementBase
は正直追いかけるのが難しいクラスなので、下記のコードをざっと眺め、Result<T>?
を管理していることを把握してみてください。
/// An internal class that handles the state of a provider.
///
/// Do not use.
abstract class ProviderElementBase<T> implements Ref<T>, Node {
/// Do not use.
ProviderElementBase(this._provider);
/* STATE */
Result<T>? _state;
/// Update the exposed value of a provider and notify its listeners.
///
/// Listeners will only be notified if [updateShouldNotify]
/// returns true.
///
/// This API is not meant for public consumption. Instead if a [Ref] needs
/// to expose a way to update the state, the practice is to expose a getter/setter.
void setState(T newState) {
final previousResult = getState();
final result = _state = ResultData(newState);
if (_didBuild) {
_notifyListeners(result, previousResult);
}
}
/// Obtains the current state, of null if the provider has yet to initialize.
///
/// The returned object will contain error information, if any.
/// This function does not cause the provider to rebuild if it someohow was
/// outdated.
///
/// This is not meant for public consumption. Instead, public API should use
/// [readSelf].
Result<T>? getState() => _state;
/// A utility for re-initializing a provider when needed.
///
/// Calling [flush] will only re-initialize the provider if it needs to rerun.
/// This can involve:
/// - a previous call to [invalidateSelf]
/// - a dependency of the provider has changed (such as when using [watch]).
///
/// This is not meant for public consumption. Public API should hide
/// [flush] from users, such that they don't need to care about invocing this function.
void flush() {
_maybeRebuildDependencies();
if (_mustRecomputeState) {
_mustRecomputeState = false;
_performBuild();
}
}
}
Provider
ではProviderElementBase
を継承したProviderElement
を利用します。
class ProviderElement<T> extends ProviderElementBase<T> implements ProviderRef<T> {
/// A [ProviderElementBase] for [Provider]
ProviderElement._(super.provider);
T get state => requireState;
set state(T newState) => setState(newState);
void create({required bool didChangeDependency}) {
final provider = this.provider as InternalProvider<T>;
setState(provider._create(this));
}
bool updateShouldNotify(T previous, T next) {
return previous != next;
}
}
ProviderElement
のcreate
は、mount()
かflush()
の中で呼び出されます。mount()
は_StateReader
のgetElement()
が初めて呼ばれるタイミング、flush()
は(ここでは)ProviderBase
のread
が呼ばれるタイミングです。これで、ref.read
の呼び出しがされた際にProvider
の引数として与えた処理が実行されるタイミングがわかりました!
ref.watch
続いて、ref.watch
のケースも確認しましょう。read
のケースで見慣れたクラスが多くなるため、ここまで読み進めた方ならば、そこまで苦労はないはずです。
class ConsumerStatefulElement extends StatefulElement implements WidgetRef {
late ProviderContainer _container = ProviderScope.containerOf(this);
Res watch<Res>(ProviderListenable<Res> target) {
return _dependencies.putIfAbsent(target, () {
final oldDependency = _oldDependencies?.remove(target);
if (oldDependency != null) {
return oldDependency;
}
return _container.listen<Res>(
target,
(_, __) => markNeedsBuild(),
);
}).read() as Res;
}
}
class ProviderContainer implements Node {
ProviderSubscription<T> listen<T>(
ProviderListenable<T> provider,
void Function(T? previous, T next) listener, {
bool fireImmediately = false,
void Function(Object error, StackTrace stackTrace)? onError,
}) {
return provider.addListener(
this,
listener,
fireImmediately: fireImmediately,
onError: onError,
onDependencyMayHaveChanged: null,
);
}
ProviderSubscription<T> _listenElement<T>(
ProviderElementBase<T> element, {
required void Function(T? previous, T next) listener,
required void Function(Object error, StackTrace stackTrace) onError,
}) {
final sub = _ExternalProviderSubscription<T>._(
element,
listener,
onError: onError,
);
element._externalDependents.add(sub);
return sub;
}
}
abstract class ProviderBase<T> extends ProviderOrFamily with ProviderListenable<T> implements ProviderOverride, Refreshable<T> {
ProviderSubscription<T> addListener(
Node node,
void Function(T? previous, T next) listener, {
required void Function(Object error, StackTrace stackTrace)? onError,
required void Function()? onDependencyMayHaveChanged,
required bool fireImmediately,
}) {
onError ??= Zone.current.handleUncaughtError;
final element = node.readProviderElement(this);
element.flush();
if (fireImmediately) {
handleFireImmediately(
element.getState()!,
listener: listener,
onError: onError,
);
}
element._onListen();
return node._listenElement(
element,
listener: listener,
onError: onError,
);
}
}
abstract class ProviderElementBase<T> implements Ref<T>, Node {
void _onListen() {
_onAddListeners?.forEach(runGuarded);
if (_didCancelOnce && !hasListeners) {
_onResumeListeners?.forEach(runGuarded);
}
}
}
/// Represents the subscription to a [ProviderListenable]
abstract class ProviderSubscription<T> {
/// Stops listening to the provider
void close();
/// Obtain the latest value emitted by the provider
T read();
}
class _ExternalProviderSubscription<T> implements ProviderSubscription<T> {
/// 省略
}
ref.watch
はref.read
と同じようにProviderElementBase
でResult<T>?
を保持しつつ、変更があった場合にコールバックを受け取るような実装になっています。ProviderElementBase._onListen
の詳細については、次のAutoDisposeProvider
で確認していくので、一旦眺める程度としてみてください。
ConsumerStatefulElement
では、listenerが動くたびにmarkNeedsBuild()
をセットしています。この処理により、ref.watch
で監視しているReulst
に変更があったとき、ConsumerStatefulWidget
を継承しているクラスのbuild
関数が呼び出されるようになります。
AutoDisposeProvider
続いて、.autoDispose
をつけたケースを確認しましょう。Provider.autoDispose
を呼び出すか、直接AutoDisposeProvider
を呼び出したケースとなります。
何もないProvider
と比べると、所々でAutoDispose用のクラスに投げ分けられていることがわかります。
class AutoDisposeProvider<T> extends InternalProvider<T> {
final T Function(AutoDisposeProviderRef<T> ref) _createFn;
T _create(AutoDisposeProviderElement<T> ref) => _createFn(ref);
AutoDisposeProviderElement<T> createElement() {
return AutoDisposeProviderElement._(this);
}
}
class Provider<T> extends InternalProvider<T> with AlwaysAliveProviderBase<T> {
final Create<T, ProviderRef<T>> _createFn;
T _create(ProviderElement<State> ref) => _createFn(ref);
ProviderElement<State> createElement() => ProviderElement._(this);
}
クラス的にはAutoDisposeProviderElement
が処理をしていそうなのですが、AutoDisposeの処理はAutoDisposeProviderElementMixin
に分割されています。
コメントやdeprecatedな処理を除いたコードを記載します。
abstract class ProviderRef<T> implements Ref<T> {
T get state;
set state(T newState);
}
abstract class AutoDisposeProviderRef<T>
extends ProviderRef<T>
implements AutoDisposeRef<T> {}
class AutoDisposeProviderElement<T>
extends ProviderElement<T>
with AutoDisposeProviderElementMixin<T>
implements AutoDisposeProviderRef<T> {
AutoDisposeProviderElement._(AutoDisposeProvider<T> super.provider) : super._();
}
/// A mixin that adds auto dispose support to a [ProviderElementBase].
mixin AutoDisposeProviderElementMixin<T> on ProviderElementBase<T> implements AutoDisposeRef<T> {
List<KeepAliveLink>? _keepAliveLinks;
KeepAliveLink keepAlive() {
final links = _keepAliveLinks ??= [];
late KeepAliveLink link;
link = KeepAliveLink._(() {
if (links.remove(link)) {
if (links.isEmpty) mayNeedDispose();
}
});
links.add(link);
return link;
}
void mayNeedDispose() {
final links = _keepAliveLinks;
if (!hasListeners && (links == null || links.isEmpty)) {
_container._scheduler.scheduleProviderDispose(this);
}
}
void runOnDispose() {
_keepAliveLinks?.clear();
super.runOnDispose();
}
}
/// A object which maintains a provider alive
class KeepAliveLink {
KeepAliveLink._(this._close);
final void Function() _close;
/// Release this [KeepAliveLink], allowing the associated provider to
/// be disposed if the provider is no-longer listener nor has any
/// remaining [KeepAliveLink].
void close() => _close();
}
AutoDisposeProviderElementMixin
はmayNeedDispose
をoverrideしています。直感的に理解できるように、この処理は「keepAlive
されていない場合、mayNeedDispose
が呼び出された際にdispose()
する」処理です。
また、runOnDispose
はProviderElementBase
のdispose()
、もしくはinvalidateSelf()
が呼び出されたタイミングで実行されます。前者は状態を保持しているProviderElementBase
が破棄されるタイミング。後者は開発者が「状態の再取得をしたい」と処理を行ったタイミングです。
ref.read
ref.read
の一連の処理の中で、都度mayNeedDispose
が呼び出されています。このため、都度dispose
が行われるます。
ただAutoDisposeProvider
が別の箇所でref.watch
されている場合、都度dispose
される処理がスキップされることがあります。
abstract class ProviderElementBase<State> implements Ref<State>, Node {
/// Whether this [ProviderElementBase] is currently listened to or not.
///
/// This maps to listeners added with [listen].
/// See also [mayNeedDispose], called when [hasListeners] may have changed.
bool get hasListeners =>
_externalDependents.isNotEmpty ||
_subscribers.isNotEmpty ||
_providerDependents.isNotEmpty;
/// The list of [ProviderSubscription]s that are linked with this element,
/// which aren't coming from another provider.
///
/// This is typically Flutter widgets or manual calls to [ProviderContainer.listen]
/// with this provider as target.
final _externalDependents = <_ExternalProviderSubscription<State>>[];
}
class ProviderContainer implements Node {
ProviderSubscription<T> _listenElement<T>(
ProviderElementBase<T> element, {
required void Function(T? previous, T next) listener,
required void Function(Object error, StackTrace stackTrace) onError,
}) {
final sub = _ExternalProviderSubscription<T>._(
element,
listener,
onError: onError,
);
element._externalDependents.add(sub);
return sub;
}
}
mixin AutoDisposeProviderElementMixin<T> on ProviderElementBase<T> implements AutoDisposeRef<T> {
void mayNeedDispose() {
final links = _keepAliveLinks;
if (!hasListeners && (links == null || links.isEmpty)) {
_container._scheduler.scheduleProviderDispose(this);
}
}
}
ref.watch
ProviderElementBase.flush()
が呼び出されたタイミングで、ProviderElementBase._mustRecomputeState
がtrue
であるとき、mayNeedDispose
が呼び出されます。
これはProviderElementBase.invalidateSelf()
による更新フラグが立っているケース、もしくはProviderElementBase
がwatch
しているProviderListenable
が更新されたケースです。後者のケースは、詳しい方がいればご指摘ただければと思うのですが、筆者の(2023年3月の)理解では「Provider
の中で、ほかのProvider
を呼び出している」ケースを指しています。というのも、ProviderElementBase
がRef
として振る舞うのは、典型的にはProvider
の_create
関数だからです。
ref.read
と異なり、ref.watch
はConsumerStatefulWidget
のbuild
関数内で呼び出されます。この呼び出しに関しては、これまでに確認しkてきた通り、ConsumerStatefulElement
のunmount
が破棄の契機となります。
仕組みとしては、ref.watch
をするとConsumerStatefulElement
の_dependencies
にProviderSubscription
が追加されます。Widgetが利用されなくなりunmount
が呼ばれると、この_dependencies
に保持された全てのProviderSubscription
がclose()
され、連鎖的にmayNeedDispose
が呼び出されるようになっています。
class ConsumerStatefulElement extends StatefulElement implements WidgetRef {
Res watch<Res>(ProviderListenable<Res> target) {
return _dependencies.putIfAbsent(target, () {
final oldDependency = _oldDependencies?.remove(target);
if (oldDependency != null) {
return oldDependency;
}
return _container.listen<Res>(
target,
(_, __) => markNeedsBuild(),
);
}).read() as Res;
}
void unmount() {
super.unmount();
for (final dependency in _dependencies.values) {
dependency.close();
}
}
}
class _ExternalProviderSubscription<T> implements ProviderSubscription<T> {
void close() {
_closed = true;
_listenedElement._externalDependents.remove(this);
_listenedElement._onRemoveListener();
}
}
abstract class ProviderElementBase<State> implements Ref<State>, Node {
void _onRemoveListener() {
_onRemoveListeners?.forEach(runGuarded);
if (!hasListeners) {
_didCancelOnce = true;
_onCancelListeners?.forEach(runGuarded);
}
mayNeedDispose();
}
}
「Elementがunmount
になった際」に破棄されるので、ドキュメントでは「Providerが参照されなくなった際」と表現されています。
ここまで確認された方ならば、この文章を納得を持って受け入れられるはずです。
mayNeedDispose
とkeepAlive
最後に、.keepAlive
について確認します。
コードを見るとわかる通り、.keepAlive
を呼び出すと、mayNeedDispose
のdispose
処理をスキップするようになります。この処理は、すでにdeprecatedになっているmaintainState
と同じです。
keepAlive
がmaintainState
より優れている点は、ref.keepAlive
の返り値を利用することで、呼び出し側が任意のタイミングでmayNeedDispose
を呼び出せるようになった点です。筆者はそこまでユースケースを思いつかないのですが、この変更により、より適切に処理を閉じれるようになるケースも存在するのではと思います。
サンプルコードにあるように、.keepAlive
を呼び出すタイミングは任意となります。
このため、処理の失敗時には.keepAlive
をつけないが成功時にはつける、といった調整を行うことで、成功したケースのみを保持することもできます。
AutoDisposeProvider
+keepAlive
とProvider
は、ともにProviderContainer
と同時にdispose
される点で、ほぼ同じであると言えます。唯一違うのは、Providerの中でProviderを呼び出す際に、AutoDisposeするProviderの中ではAutoDisposeされるProviderを呼び出す必要がある、という点です。
AutoDisposeされるProviderを重ねることで、ProviderContainer
の中に不要なProviderを積み上げなくて良くなります。そこまで問題になるケースがないような気がしますが、.autoDispose
を活用することで、より軽快なアプリケーションが実現できるかもしれません。
おわりに
ここまでお読みいただき、ありがとうございました。単にRiverpodの.autoDispose
ありなし、そして.keepAlive
の振る舞いについて確認したかっただけなのですが、非常に長い記事となってしまいました。まさかという感じです。
Riverpodは、非常に良くできたライブラリだと思います。
どんどん理解を深めて、より楽しく、快適なFlutterライフを送りましょう!
Discussion