Flutter state management based on riverpod

Time:2022-5-26

Flutter state management based on riverpod

original text

https://itnext.io/flutter-sta…

code

https://github.com/iisprey/ri…

reference resources

text

Flutter state management based on riverpod

As I promised last week, I will show you my own path to a final state management solution

Riverpod + StateNotifier + Hooks + Freezed

Riverpod is great! But there are not many good examples. Only the most basic, that’s all. This time, I try to make an example both understandable and complex. My goal is to teach you when to use riverpod and how to use it through this example. Although I simplified the process. I wish you like it.

motivation

Flutter state management based on riverpod

What are we going to do in this example?

We just need to get some data from the API, and then sort and filter them in the UI

Basically, we will;

  1. Create simple and complex providers and combine them
  2. Create simple and complex providers and combine them
  3. Use AsyncValue object and show async value in the UI using when method
  4. Use the asyncvalue object and use the when method in the UI to display the async value
  5. Also, create freezed objects for immutable object solution
  6. At the same time, create frozen objects for immutable objects / solutions

Let’s start.

Create API service

Note: I didn’t find a good API model to use filtering because I added these roles myself. Forgive me for saying that

final userService = Provider((ref) => UserService());

class UserService {
  final _dio = Dio(BaseOptions(baseUrl: 'https://reqres.in/api/'));

  Future<List<User>> getUsers() async {
    final res = await _dio.get('users');
    final List list = res.data['data'];
    // API didn't have user roles I just added by hand (it looks ugly but never mind)
    list[0]['role'] = 'normal';
    list[1]['role'] = 'normal';
    list[2]['role'] = 'normal';
    list[3]['role'] = 'admin';
    list[4]['role'] = 'admin';
    list[5]['role'] = 'normal';
    return list.map((e) => User.fromJson(e)).toList();
  }
}

usefreezedandjson_serializableCreate immutable model

We just need to create one

part 'user.freezed.dart';
part 'user.g.dart';

@freezed
class User with _$User {
  @JsonSerializable(fieldRename: FieldRename.snake)
  const factory User({
    required int id,
    required String email,
    required String firstName,
    required String lastName,
    required String avatar,
    @JsonKey(unknownEnumValue: Role.normal) required Role role,
  }) = _User;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

If you want to know more about freezed, please check out this article.

https://iisprey.medium.com/ho…

Get from servicesvalue

You can think about it,AsyncValueWhat is it? It’s just a union class that helps us deal with the state of our values. It provides an off the shelf provider package.

I’ll explain it in detail in the next article, but for now, that’s all.

final usersProvider = StateNotifierProvider.autoDispose<UserNotifier, AsyncValue<List<User>>>((ref) {
  return UserNotifier(ref);
});

class UserNotifier extends StateNotifier<AsyncValue<List<User>>> {
  final AutoDisposeStateNotifierProviderRef _ref;

  late final UserService _service;

  UserNotifier(this._ref) : super(const AsyncValue.data(<User>[])) {
    _service = _ref.watch(userService);
    getUsers();
  }

  Future<void> getUsers() async {
    state = const AsyncValue.loading();
    final res = await AsyncValue.guard(() async => await _service.getUsers());
    state = AsyncValue.data(res.asData!.value);
  }
}

Create sorting and filter providers

enum Role { none, normal, admin }
enum Sort { normal, reversed }

final filterProvider = StateProvider.autoDispose<Role>((_) => Role.none);
final sortProvider = StateProvider.autoDispose<Sort>((_) => Sort.normal);

Get the list from the provider, filter it, and then sort it using another provider

final filteredAndSortedUsersProvider = Provider.autoDispose.family<List<User>, List<User>>((ref, users) {
  final filter = ref.watch(filterProvider);
  final sort = ref.watch(sortProvider);

  late final List<User> filteredList;

  switch (filter) {
    case Role.admin:
      filteredList = users.where((e) => e.role == Role.admin).toList();
      break;
    case Role.normal:
      filteredList = users.where((e) => e.role == Role.normal).toList();
      break;
    default:
      filteredList = users;
  }

  switch (sort) {
    case Sort.normal:
      return filteredList;
    case Sort.reversed:
      return filteredList.reversed.toList();
    default:
      return filteredList;
  }
});

Display everything in the user interface

class HomePage extends ConsumerWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final users = ref.watch(usersProvider);
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: const Text('Users'),
      ),
      body: RefreshIndicator(
        onRefresh: () async => await ref.refresh(usersProvider),
        child: users.when(
          data: (list) {
            final newList = ref.watch(filteredAndSortedUsersProvider(list));
            if (newList.isEmpty) {
              return const Center(child: Text('There is no user'));
            }
            return ListView.builder(
              itemCount: newList.length,
              itemBuilder: (_, i) {
                final user = newList[i];
                return ListTile(
                  minVerticalPadding: 25,
                  leading: Image.network(user.avatar),
                  title: Text('${user.firstName} ${user.lastName}'),
                  trailing: Text(user.role.name),
                );
              },
            );
          },
          error: (_, __) => const Center(child: Text('err')),
          loading: () => const Center(child: CircularProgressIndicator()),
        ),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
      floatingActionButton: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Consumer(
                builder: (_, ref, __) {
                  final sort = ref.watch(sortProvider.state);
                  return ElevatedButton(
                    onPressed: () {
                      if (sort.state == Sort.reversed) {
                        sort.state = Sort.normal;
                      } else {
                        sort.state = Sort.reversed;
                      }
                    },
                    child: Text(
                      sort.state == Sort.normal
                          ? 'sort reversed'
                          : 'sort normal',
                    ),
                  );
                },
              ),
            ),
          ),
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Consumer(
                builder: (_, ref, __) {
                  final filter = ref.watch(filterProvider.state);
                  return ElevatedButton(
                    onPressed: () {
                      if (filter.state == Role.admin) {
                        filter.state = Role.none;
                      } else {
                        filter.state = Role.admin;
                      }
                    },
                    child: Text(
                      filter.state == Role.admin
                          ? 'remove filter'
                          : 'filter admins',
                    ),
                  );
                },
              ),
            ),
          ),
        ],
      ),
    );
  }
}

end

If you think of this example as an e-commerce application, then this example is more meaningful

I am notriverpodMy master. Just learn and share my experience, so please if you know a better way to use itriverpodPlease let us know!

Example GitHub project

This is the source code.

https://github.com/iisprey/ri…

Thank you for reading


© Cat brother

Flutter state management based on riverpod