riverpod
riverpod copied to clipboard
`ref.exists()` returns `true` when manually invalidate a provider.
Describe the bug
It appears that ref.exist(someProvider)
still returns true
when manually invalidate a provider, and when try to use the invalidated provider onResume
gets called. While when we set keepAlive
to true
or use AutoDisposeNotifier
everything works as expected.
This behavior is found when using / not using generated code.
Original issue #2729
To Reproduce
Code Generation Sample
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'main.g.dart';
@Riverpod(keepAlive: true)
int fetchItem(FetchItemRef ref) {
// this will be called on invalidate.
ref.onDispose(() => print('dispose'));
ref.onCancel(() => print('onCancel'));
// but this will be called when we use
// the provider after invalidating
ref.onResume(() => print('onResume'));
return 5;
}
void main() {
runApp(
const ProviderScope(child: MaterialApp(home: MyHomePage())),
);
}
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: () {
print('does exist: ${ref.exists(fetchItemProvider)}');
}),
body: Center(
child: TextButton(
onPressed: () {
print('Navigate To Next Page');
Navigator.of(context).push(MaterialPageRoute(builder: (_) => MyHomePage2()));
},
child: Text('Navigate To Next Page'),
),
),
);
}
}
class MyHomePage2 extends ConsumerStatefulWidget {
const MyHomePage2({super.key});
@override
ConsumerState<MyHomePage2> createState() => _MyHomePage2State();
}
class _MyHomePage2State extends ConsumerState<MyHomePage2> {
ProviderContainer? _container;
@override
void initState() {
super.initState();
}
@override
void dispose() {
_container!.invalidate(fetchItemProvider);
super.dispose();
}
@override
Widget build(BuildContext context) {
_container = ProviderScope.containerOf(context);
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(onPressed: () {
print('does exist: ${ref.exists(fetchItemProvider)}');
}),
body: Center(
child: Text(ref.watch(fetchItemProvider).toString()),
),
);
}
}
No Code Generation Sample with explanation
To Reproduce
- run the code
- Click Exist Fab
- Navigate to next page
- Click the + Fab to increment state
- Click Exist
- Navigate Back
- Click Exist
Logs
flutter: does exist: false
flutter: internal State: 5
flutter: Navigate To Next Page
flutter: does exist: true
flutter: internal State: 7
flutter: dispose
flutter: does exist: true // the instance still exists and the internal state is preserved.
flutter: internal State: 7
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class FetchItemsNotifier extends Notifier<int> {
int internalState = 5;
@override
build() {
// this will be called on invalidate.
ref.onDispose(() => print('dispose'));
ref.onCancel(() => print('onCancel'));
// but this will be called when we use
// the provider after invalidating
ref.onResume(() => print('onResume'));
return 5;
}
void add() {
state = state + 1;
internalState = state;
}
}
final fetchItemProvider = NotifierProvider<FetchItemsNotifier, int>(FetchItemsNotifier.new);
void main() {
runApp(
const ProviderScope(child: MaterialApp(home: MyHomePage())),
);
}
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Text('Exist'),
onPressed: () {
print('does exist: ${ref.exists(fetchItemProvider)}');
print('internal State: ${ref.read(fetchItemProvider.notifier).internalState}');
},
),
body: Center(
child: TextButton(
onPressed: () {
print('Navigate To Next Page');
Navigator.of(context).push(MaterialPageRoute(builder: (_) => MyHomePage2()));
},
child: Text('Navigate To Next Page'),
),
),
);
}
}
class MyHomePage2 extends ConsumerStatefulWidget {
const MyHomePage2({super.key});
@override
ConsumerState<MyHomePage2> createState() => _MyHomePage2State();
}
class _MyHomePage2State extends ConsumerState<MyHomePage2> {
ProviderContainer? _container;
@override
void initState() {
super.initState();
}
@override
void dispose() {
_container!.invalidate(fetchItemProvider);
super.dispose();
}
@override
Widget build(BuildContext context) {
_container = ProviderScope.containerOf(context);
return Scaffold(
appBar: AppBar(),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: 'Exist',
child: Text('Exist'),
onPressed: () {
print('does exist: ${ref.exists(fetchItemProvider)}');
print('internal State: ${ref.read(fetchItemProvider.notifier).internalState}');
},
),
FloatingActionButton(
child: Text('+'),
onPressed: () {
ref.read(fetchItemProvider.notifier).add();
},
),
],
),
body: Center(
child: Text(ref.watch(fetchItemProvider).toString()),
),
);
}
}
When we switch to using AutoDisposeNotifier
the internal state is resets (which means the old provider instance are removed from the container) and ref.exists()
return false
. which will produce the following logs:
**Logs When Using AutoDisposeNotifier
**
flutter: does exist: false
flutter: internal State: 5
flutter: dispose // got disposed after accessing the internal state of the provider when no active listener registered.
flutter: Navigate To Next Page
flutter: does exist: true
flutter: internal State: 8
flutter: dispose
flutter: does exist: false // auto dispose works as expected.
flutter: internal State: 5 // a new AutoDisposeNotifier instance created
flutter: dispose // got disposed after accessing the internal state of the provider when no active listener registered.