Flutter Riverpod Example – Category Selection

In the latest blog posts, I wrote about Flutter Hooks and the Provider to simplify state management in Flutter. We described how to simplify the modal dialog selector of categories with Flutter Hooks and with the Provider. However, there is also an option to combine both. Riverpod is a Provider but different! Riverpod has multiple benefits, such as supporting multiple providers of the same type, combining asynchronous providers, and adding providers from anywhere. Furthermore, it provides a way to combine the Provider with Hooks! In this blog post, we will show an example showing how to use Flutter Riverpod.

Riverpod different packages

Riverpod provides multiple packages to use state management.

App typePackage nameDescription
Flutter + flutter_hookshooks_riverpodA way to use both flutter_hooks and Riverpod together while offering small extras.
Flutter onlyflutter_riverpodA basic way of using Riverpod, which does not depend on flutter_hooks.
Dart only (No Flutter)riverpodA version of Riverpod with all the classes related to Flutter was stripped out.
Table describing the different packages from the documentation!

They have even included an image to make it even more simple!

Image describing when to use which package from the getting started documents of Riverpod

Since we are using Flutter, we are interested in both hooks_riverpod and flutter_riverpod. So we will first provide an example with flutter_riverpod and then rewrite the same use case with hooks_riverpod to show the differences!

Example with Flutter Riverpod

Before we can start with coding, we are going to add a dependency to the project.

dependencies:
  flutter_riverpod: ^1.0.0-dev.6

Do not forget to install the dependency, running the following command:

flutter pub get

That is it! We can now start with the example. The example we are going to build is a list of selectable categories. Underneath the checkboxes is a list that displays the currently selected values. This means that two Widgets need access to the list of categories and the currently selected values.

For this, we are going to introduce a StateNotifier. The StateNotifier is of a type. We are going to maintain a map of String to Booleans. In our case, the Strings are the categories, and the boolean maintains whether the value is selected or not. Finally, we will introduce a toggle method that will toggle the selected value of a category. Afterward, we will trigger the updates by setting state = state.

class CategoryList extends StateNotifier<Map<String, bool>> {
  CategoryList(Map<String, bool> state) : super(state);

  void toggle(String item) {
    state[item] = !state[item];
    state = state;
  }
}

The StateNotifier can be used to create a StateNotifierProvider. For this, we created a helper method that converts a list of Strings into the initial state of the StateNotifier.

final categoryListProvider =StateNotifierProvider((_) => createCategoryList(["Banana", "Pear", "Apple", "Strawberry", "Pineapple"]));

CategoryList createCategoryList(List<String> values) {
  final Map<String, bool> categories = Map();
  values.forEach((value) {
    categories.putIfAbsent(value, () => false);
  });
  return CategoryList(categories);
}

We can use StateNotifierProvider to create two different providers. One for the list of all categories, another one for the list of currently selected values. We implement a list with a Provider and access the state of the CategoryList through the categoryListProvider.

final selectedCategories = Provider((ref) => ref
    .watch(categoryListProvider)
    .entries
    .where((MapEntry<String, bool> category) => category.value)
    .map((e) => e.key)
    .toList());

final allCategories = Provider((ref) => ref.watch(categoryListProvider.state).keys.toList());

We now have the state we need for the Widgets, so let’s get started! Now we can create a new Widget that extends the ConsumerWidget. In the ConsumerWidget, we have to override a different build function as a normal Widget. The method provides a watch function that we can use to access the provider we created. With ref.watch(selectedCategories), we can access the list of select categories. Then with the ListView, we will display the selected values.

class SelectedCategories extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final categoryList = ref.watch(selectedCategories);

    return Flexible(
      child: ListView.builder(
          itemCount: categoryList.length,
          itemBuilder: (BuildContext context, int index) {
            return Padding(
              padding: const EdgeInsets.all(8.0),
              child: Text(categoryList[index]),
            );
          }),
    );
  }
}

Updating the state

We can now show the selected categories, but we have no way yet to select the categories. We again have to convert the Widget into a ConsumerWidget. Here we are going to access both the list of all categories as the selected list. This is done in the same way as in the SelectedCategories Widget. What we are interested in now is in selecting the categories. Through the watch function, we can access the CategoryList and toggle the category with the toggle function.

class CategoryFilter extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final selectedCategoryList = ref.watch(selectedCategories);
    final categoryList = ref.watch(allCategories);
    final provider = ref.watch(categoryListProvider.notifier);

    return Flexible(
      child: ListView.builder(
          itemCount: categoryList.length,
          itemBuilder: (BuildContext context, int index) {
            return CheckboxListTile(
              value: selectedCategoryList.contains(categoryList[index]),
              onChanged: (bool? selected) {
                provider.toggle(categoryList[index]);
              },
              title: Text(categoryList[index]),
            );
          }),
    );
  }
}

That is all we need to do! If you are interested in the code so far, that is available here on Github. In the next part, we are going to introduce Riverpod hooks!

Example with Riverpod Hooks

For the second example, we have to adjust our dependencies. We are going to add the dependency for Hooks Riverpod. Furthermore, we need to add the dependency on Flutter Hooks itself.

dependencies:
  flutter:
    sdk: flutter
  flutter_hooks: ^0.18.0
  hooks_riverpod: ^1.0.0-dev.6

There are only a few things we have to rewrite the previous example with hooks. First, we can change the ConsumerWidgets into HookConsumerWidgets, which allows us to make use of hooks. However, we also still have access to the Widget ref function to perform the same actions.

class SelectedCategories extends HookConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final categoryList = ref.watch(selectedCategories);
    return Flexible(
      child: ListView.builder(
          itemCount: categoryList.length,
          itemBuilder: (BuildContext context, int index) {
            return Padding(
              padding: const EdgeInsets.all(8.0),
              child: Text(categoryList[index]),
            );
          }),
    );
  }
}

We can repeat the same trick for the other widget. We convert it to a HookConsumerWidgets and again use the ref.watch to access both the complete list as the selected list. Since we still have access to the build context, we can toggle the values of the list in

class CategoryFilter extends HookConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final selectedCategoryList = ref.watch(selectedCategories);
    final categoryList = ref.watch(allCategories);
    final provider = ref.watch(categoryListProvider.notifier);

    return Flexible(
      child: ListView.builder(
          itemCount: categoryList.length,
          itemBuilder: (BuildContext context, int index) {
            return CheckboxListTile(
              value: selectedCategoryList.contains(categoryList[index]),
              onChanged: (bool? selected) {
                provider.toggle(categoryList[index]);
              },
              title: Text(categoryList[index]),
            );
          }),
    );
  }
}

With the StateNotifier, we can keep the state in one place. Another benefit is that we can create multiple providers of the StateNotifier. Now can easily keep the full list and the selected list available. You can find the full code on Github. If you have any questions, feel free to ask them in the comments!

Leave a Reply