I've just started learning Riverpod. I am trying to use FutureProvider with Riverpod to execute time-consuming processes such as initState and Button's onTap. I don't want to keep this FutureProvider unless it's needed, so I've added autoDisposed, but if I do that, it will be immediately disposed when I run ref.read in initState or onTap. I expect that it will not be disposed until the processing is finished, but how should I fix this?
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
const ProviderScope(
child: MaterialApp(
home: Sample(),
),
),
);
}
final dataProvider =
FutureProvider.autoDispose.family<int, int>((ref, base) async {
ref.onDispose(() {
print('onDispose');
});
await Future.delayed(const Duration(seconds: 3));
return base * 2; // just an example; actually retrieved from the server.
});
class Sample extends ConsumerStatefulWidget {
const Sample({super.key});
@override
ConsumerState<Sample> createState() => _SampleState();
}
class _SampleState extends ConsumerState<Sample> {
int _sampleResult = 0;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
ref.read(dataProvider(2)).when(
data: (value) {
setState(() {
_sampleResult = value;
});
print(value);
},
error: (error, stackTrace) {
print(error);
},
loading: () {});
});
}
@override
Widget build(BuildContext context) {
return Row(
children: [
Text('$_sampleResult'),
TextButton(
onPressed: () {
ref.read(dataProvider(_sampleResult)).when(
data: (value) {
setState(() {
_sampleResult = value;
});
},
error: (error, stackTrace) {
_sampleResult -= 10;
},
loading: () {},
);
},
child: const Text('test button'))
],
);
}
}
You do not have to declare the state variable _sampleResult
and then initialise it in the initState
. Instead, you simply use ref.watch(futureProvider
which returns an AsyncValue<YourDataType>
and use when
or the new pattern matching to display the data/loading/error
state accordingly.
As long as your provider is being watched/listened, it will not be disposed.
If you want to mutate the state, you could use Notifier/AsyncNotifier
and NotifierProvider/AsyncNotifierProvider
.
I have included an example below.
class DataNotifier extends FamilyAsyncNotifier<int, int> {
@override
FutureOr<int> build(int arg) async {
ref.onDispose(() => print('onDispose'));
await Future<void>.delayed(const Duration(seconds: 3));
return arg * 2;
}
Future<void> increment() async {
// there is a handy update method in AsyncNotifier which let you mutate the state without dealing with the loading/error state
await update((value) => value + 1);
}
}
final dataProvider = AsyncNotifierProviderFamily<DataNotifier, int, int>(DataNotifier.new);
class Sample extends ConsumerWidget {
const Sample({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// watch your provider inside build
final data = ref.watch(dataProvider(10));
return data.when(
data: (value) => Row(
children: [
Text('$value'),
TextButton(
// use ref.read(yourProvider.notifier).method() to mutate your provider's state
onPressed: () async => ref.read(dataProvider(10).notifier).increment(),
child: const Text('test button'),
),
],
),
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('$error'),
);
}
}