Riverpod AsyncNotifier may sound a litle confusing if you are just coming to Riverpod. After all Riverpod and the latest versions they introduced many concepts. AsyncNotifier works with AsyncNotifierProvider. AsyncNotifier is a wrapper around your state or data which works asyncronomously(await and async).
In general, you may be just satisfied with Notifier. It's suitable if you don't do any network request or retreive data from the database. If you do network request, we may need to use AsyncNotifier.
Simply speaking
AsyncNotifier is used for network call or retreiving data from storage
And how to use it then?
Notifier
Well, first we know the Riverpod 2.0 introduce a build() method. This method defines the return type of a provider.
This class is a Notifier class and see the build() method. It returns an empty List. You may also say it returns List Notifier.
I know it's confusing
But this is Riverpod
Simply speaking
This is a Notifier class which exposes a List to NotifierProvider
But even if you don't know these terms, just remember, this class has build method and it returns a List.
Learn about Riverpod 2.0 Stream Provider and Future Provider.
Now let's dive into AsyncNotifier !!
AsyncNotifier
It's used for network call. We know that network type returns Future type. So our above Todo class build() method return type has to change now.
Look at the build() method first, we see that it returns FutureOr which is like a Future. Even though _fetchTodo() returns a List(look at the top), but since it's coming from network or external world, we use FutureOr
AsyncNotifier also comes with some helping method like
- AsyncValue.loading()
- AsyncValue.guard()
- AsyncValue.data()
AsyncValue.loading()
We don't directly use AsyncValue.loading() in inside build() method. We use it before we actually start to load or make request, you may think it as if its the loading icon.
AsyncValue.loading() is like a circular progress indicator.
AsyncValue.guard()
It's used right after calling AsyncValue.loading(). Gaurd() method helps posting or deleting or updating network request.
In general you will call addTodo() method from UI using AsyncNotifier. And what does it mean?
Here you see we are calling asyncTodosProvider. In this case asyncTodosProvider is our AsyncNotifier.
The last line is read like this
We are passing an AsyncNotifier to AsyncNotifierProvider
ref.watch() may take NotifierProvider or AsyncNotifierProvider
AsyncValue.data()
This is used for data assignment. AsyncValue.data() takes any data type. The data you pass to data() method, it should match the return type of build() method.
See a complete code AsyncNotifier
We will create a AsyncNotifierController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
@riverpod class AsyncNotifierScreenController extends _$AsyncNotifierScreenController { @override FutureOr<String> build() async { final response = await http.get(Uri.parse('https://random-word-api.herokuapp.com/word')); if (response.statusCode == 200) { final jsonResponse = jsonDecode(response.body); final randomWord = jsonResponse[0]; return randomWord; } else { print('Request failed with status: ${response.statusCode}.'); } return ''; } void delete() { state = const AsyncValue.data(''); } Future<void> setNewWord() async { state = const AsyncLoading(); state = AsyncValue.data(await getNewWord()); } Future<String> getNewWord() async { final response = await http.get(Uri.parse('https://random-word-api.herokuapp.com/word')); if (response.statusCode == 200) { final jsonResponse = jsonDecode(response.body); final randomWord = jsonResponse[0]; return randomWord; } else { print('Request failed with status: ${response.statusCode}.'); } return 'ERROR'; } } |
Here we all these three methods return Future type since we are doing a network request. Here we have AsyncLoading() function is available which is coming from Riverpod. This is used as state during data loading. Because of this you can use show CircularProgressIndicator inside when() function of Riverpod providers.
Look at setNewWord() method and it has loading mechanism and setting data mechanism. Simply AsyncLoading() and AsyncValue is doing the job.
We can easily call setNewWord() from the UI using our provider.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
void main() { runApp( ProviderScope(child: MyApp())); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( home: AsyncNotifierScreen(), ); } } class AsyncNotifierScreen extends ConsumerWidget { const AsyncNotifierScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(asyncNotifierScreenControllerProvider); return Scaffold( backgroundColor: Colors.grey, body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ state.when( data: (data) => Text( data, style: const TextStyle( fontSize: 40, fontWeight: FontWeight.bold, color: Colors.white, ), ), loading: () => const CircularProgressIndicator(), error: (error, stackTrace) => Text( '$error', style: const TextStyle( fontSize: 40, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ], )), floatingActionButton: Row( mainAxisSize: MainAxisSize.min, children: [ FloatingActionButton.extended( label: Text('Delete'), heroTag: 'delete', onPressed: () { ref.read(asyncNotifierScreenControllerProvider.notifier).delete(); }, ), const SizedBox(width: 20), FloatingActionButton.extended( label: Text('Get'), heroTag: 'get', onPressed: () async { await ref .read(asyncNotifierScreenControllerProvider.notifier) .setNewWord(); }, ), ], ), ); } } @riverpod class AsyncNotifierScreenController extends _$AsyncNotifierScreenController { @override FutureOr<String> build() async { final response = await http.get(Uri.parse('https://random-word-api.herokuapp.com/word')); if (response.statusCode == 200) { final jsonResponse = jsonDecode(response.body); final randomWord = jsonResponse[0]; return randomWord; } else { print('Request failed with status: ${response.statusCode}.'); } return ''; } void delete() { state = const AsyncValue.data(''); } Future<void> setNewWord() async { state = const AsyncLoading(); state = AsyncValue.data(await getNewWord()); } Future<String> getNewWord() async { final response = await http.get(Uri.parse('https://random-word-api.herokuapp.com/word')); if (response.statusCode == 200) { final jsonResponse = jsonDecode(response.body); final randomWord = jsonResponse[0]; return randomWord; } else { print('Request failed with status: ${response.statusCode}.'); } return 'ERROR'; } } |
The course that uses AsyncNotifier for real app using Riverpod state management. Click here to know more about the Riverpod course.
AsyncNotifierProvider Dive Deep
Let's take a closer look of the AsyncNotifier through below example.
Here build method returns fetchCourseList()
which is a FutureOr
type. And it's data type is List<CourseItem>
. Since we it's Future<List<CourseItem>>
the generated notifier is also AsyncNotifier
. And how do we know this? Let's take a look at the generated riverpod code by code generation tool.
Here you see the last line, AsyncNotifier<List<CourseItem>?>. AsyncNotifier
just tells you the data you want to get is Future
or FutureOr
.
So we can say that AsyncNotifier<data type> equivalent to Future<data type>
. From last time we also see the we have a typedef _$HomeCourseList.
This typedef is shorthand notation for our data state AsyncNotifier<List<CourseItem>?>. This data state tells us what kind of provider to generate. In this case we are generating AsyncNotifierProvider
which you can see from line 3 in the above picture.
So our homeCourseListProvider is a AsyncNotifierProvider type. And this provider is auto generated when you mention the build() method return type as Future or FutureOr.
In the course, you will learn how to use more complex situation using AsyncNotifier and FutureOr data type.