I want to create a provider that allows me to receive arguments to update the result, similar to FamilyNotifier, but also a provider that gets disposed once no listeners are attached, such as when I pop the current screen, just like AutoDisposeNotifier.
I'm trying to combine both behaviors using AutoDisposeFamilyNotifier, but I’ve noticed that it doesn’t work as expected. This provider is disposed of each time I change the argument passed to it, even if the same argument was used before. I’m not sure if this is the intended behavior, but ideally, I would like the provider to be disposed of only when I pop the current screen.
My goal is to create a new state each time I navigate to the screen where this provider is used, cache results for arguments that have already been passed, and dispose the provider when I pop the current screen.
Is there a way to achieve this? I tried using ref.keepAlive, but this essentially turns AutoDisposeFamilyNotifier into a FamilyNotifier, preventing the provider from being disposed of when I pop the screen.
I’ve included a minimal example to illustrate the issue more clearly.
Thanks in advance!
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MainApp()));
}
final counterProvider = StateProvider<int>((_) => 0);
final resultProvider = NotifierProvider.family.autoDispose<CounterResultNotifier, ({bool loading, int result}), int>(CounterResultNotifier.new);
class CounterResultNotifier extends AutoDisposeFamilyNotifier<({bool loading, int result}), int> {
@override
({bool loading, int result}) build(int arg) {
print('build with arg: $arg');
ref.onDispose(() {
print('dispose with arg: $arg');
});
update();
return (loading: true, result: 0);
}
Future<void> update() async {
await Future.delayed(Duration(seconds: 2));
state = (loading: false, result: arg);
}
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen()
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FilledButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (context) => CounterScreen())),
child: Text('Go to Counter')
)
],
),
),
);
}
}
class CounterScreen extends ConsumerWidget {
const CounterScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: Text('Riverpod Issue'),),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 30.0,
children: [
Consumer(
builder: (_, ref, __) {
final counter = ref.watch(counterProvider);
final result = ref.watch(resultProvider(counter));
if(result.loading) {
return CircularProgressIndicator();
}
return Text('${result.result}');
}
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 60,
children: [
FloatingActionButton(
heroTag: "remove",
onPressed: () => ref.read(counterProvider.notifier).update((state) => state - 1),
child: Icon(Icons.remove)
),
FloatingActionButton(
heroTag: "add",
onPressed: () => ref.read(counterProvider.notifier).update((state) => state + 1),
child: Icon(Icons.add)
)
],
)
],
),
),
);
}
}