The code below takes user input, and prints it in the upper case after a delay of 1s.
Minimal reproducible code:
class FooPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncResult = ref.watch(resultProvider);
return Scaffold(
body: Column(
children: [
TextField(onChanged: (s) => ref.read(queryProvider.notifier).state = s),
asyncResult.when(
data: Text.new,
error: (e, s) => Text('Error = $e'),
loading: () => Text('Loading...'),
),
],
),
);
}
}
final stringProvider = FutureProvider.family<String, String>((ref, query) async {
await Future.delayed(Duration(seconds: 1));
return query.toUpperCase();
});
final queryProvider = StateProvider<String>((ref) => '');
final resultProvider = FutureProvider<String>((ref) async {
final query = ref.watch(queryProvider);
return ref.watch(stringProvider(query).future);
});
After running the code,
- Enter any text (say
a
) and wait for the output (the upper case) - Enter another text (say
b
), and wait for the output (the upper case) - Press the backspace (i.e. delete the character
b
) and you'll notice that the provider goes in the loading state for a fraction of seconds (which makes theloading
widget to get called).
This issue is happening on the 2.0.2
version. So, how can I get the previous value so that I can consistently show data
once a data is fetched?
CodePudding user response:
after upgrading my riverpod to 2.0.2, i now can see the behavior. The main reason for that is because of the 1 second delay. And there is no way you can eliminate that loading unless you remove the delay because everytime there is a change in your query, the future provider is being called, i tried it and its working fine. Now for your main question that how to access the previous state of a provider, you can do that using ref.listen()
. here below it the demo on how you can access it.
class FooPage extends ConsumerWidget {
const FooPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
ref.listen<AsyncValue<String>>(resultProvider, (previous, next) {
//* you can access the previous state here
if (previous?.value != '' && previous?.value != null) {
ref.read(upperCaseQuery.notifier).state = previous?.value ?? '';
}
});
final resultValue = ref.watch(resultProvider);
final upperCased = ref.watch(upperCaseQuery.state).state;
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(onChanged: (s) {
ref.read(queryProvider.notifier).state = s;
}),
//previous value of the the provider
Text(upperCased),
resultValue.when(
data: (value) => Text(value),
error: (e, s) => Text('Error = $e'),
loading: () => const Text('Loading...'),
),
],
),
),
);
}
}
final queryProvider = StateProvider<String>((ref) => 'default');
final resultProvider = FutureProvider.autoDispose<String>((ref) async {
final query = ref.watch(queryProvider);
await Future.delayed(const Duration(seconds: 1));
final upperCased = query.toUpperCase();
return upperCased;
}, name: 'result provider');
final upperCaseQuery = StateProvider<String>((ref) => '', name: 'upper cased');
CodePudding user response:
If you move await Future.delayed(const Duration(seconds: 1));
from stringProvider
to resultProvider
everything will work as you would expect.
Also, let me offer you a variant of code modernization:
/// Instead `stringProvider`.
Future<String> stringConvert(String query) async {
await Future.delayed(const Duration(seconds: 1));
return query.toUpperCase();
}
final queryProvider = StateProvider<String>((ref) {
return '';
});
final resultProvider = FutureProvider<String>((ref) async {
final query = ref.watch(queryProvider);
return stringConvert(query);
});