Flutter Riverpod Example – Category Selection

In the latest blog posts, I have been writing 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 a support for 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 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. 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: ^0.12.1

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 categories that are selectable. 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 to 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. We are going to 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> {
  CategoryList(Map 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 values) {
  final Map 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.state)
    .entries
    .where((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 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,
      T Function(ProviderBase provider) watch) {
    final categoryList = 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 context, we can access the CategoryList and toggle the category with the toggle function: context.read(categoryListProvider).toggle(categoryList[index]);

class CategoryFilter extends ConsumerWidget {
  @override
  Widget build(BuildContext context,
      T Function(ProviderBase provider) watch) {
    final selectedCategoryList = watch(selectedCategories);
    final categoryList = watch(allCategories);

    return Flexible(
      child: ListView.builder(
          itemCount: categoryList.length,
          itemBuilder: (BuildContext context, int index) {
            return CheckboxListTile(
              value: selectedCategoryList.contains(categoryList[index]),
              onChanged: (bool selected) {
                context.read(categoryListProvider).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_hooks: ^0.15.0
  hooks_riverpod: ^0.12.1

There are only a few things we have to rewrite the previous example with hooks. We can change the ConsumerWidgets into HookWidgets, which allows us to make use of hooks. We no longer have access to the watch function provided by the ConsumerWidget. This means we have to access the list in another way. For this, we can use the hook provided by Hooks Riverpod: useProvider.

class SelectedCategories extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final categoryList = useProvider(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 HookWidget and use the useProvider hook 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 HookWidget {
  @override
  Widget build(BuildContext context) {
    final selectedCategoryList = useProvider(selectedCategories);
    final categoryList = useProvider(allCategories);

    return Flexible(
      child: ListView.builder(
          itemCount: categoryList.length,
          itemBuilder: (BuildContext context, int index) {
            return CheckboxListTile(
              value: selectedCategoryList.contains(categoryList[index]),
              onChanged: (bool selected) {
                context.read(categoryListProvider).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. The full code can be found on Github. If you have any questions, feel free to ask them in the comments!

Leave a Reply