Riverpod AsyncNotifier Explained

Created At: 2023-06-21 22:49:44 Updated At: 2023-09-08 18:42:47

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

  1. AsyncValue.loading()
  2. AsyncValue.guard()
  3. 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 

@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.

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.

Comment

Add Reviews