Flutter state management based on Riverpod

会煮咖啡的猫
中文

original

https://itnext.io/flutter-state-management-with-riverpod-ef8d4ef77392

code

https://github.com/iisprey/riverpod_example

refer to

text

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

Riverpod + StateNotifier + Hooks + Freezed

Riverpod is amazing! But there are not many good examples. Only the most basic, that's all. This time, I tried to make an example both understandable and complex. My purpose is to teach you when to use Riverpod, and how to use it, through this example. Although I simplified the process. Hope you like it!

motivation

What are we going to do in this example?

We just need to get some data from the API and 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 display the async value in the UI using the when method
  5. Also, create freezed objects for immutable object solution
  6. Also, create frozen objects for immutable objects/solutions

let us start!

Create an API service

Note: I didn't find a good API model to use the filtering functionality because I added the 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();
  }
}

Create immutable models using freezed and json_serializable

We just need to create a

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 learn more about freezed, check out this article.

https://iisprey.medium.com/how-to-handle-complex-json-in-flutter-4982015b4fdf

value from service

You can think, AsyncValue is 061deed02702e1? It's just a union class that helps us handle the state of our values. It provides a ready-made provider package.

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

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 a sort and filter provider

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 fetched list from the provider and filter it, then sort them using other providers

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',
                    ),
                  );
                },
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Finish

This example makes a lot more sense if you think of it as an e-commerce application

I am not the guru of riverpod Just learning and sharing my experience, so please if you know a better way to use riverpod please let us know!

Sample Github project

Here is the source code.

https://github.com/iisprey/riverpod_example

thanks for reading


© Cat Brother

订阅号

阅读 695

东西
前端全栈 nodejs vue react ssr 同构 pm2 webpack linux 一发不可收拾~

全栈开发者 最近专注于 Flutter/Golang/Nodejs/K8s/CICD/基于Docker前端基础建设 , 喜欢技术分享

623 声望
105 粉丝
0 条评论

全栈开发者 最近专注于 Flutter/Golang/Nodejs/K8s/CICD/基于Docker前端基础建设 , 喜欢技术分享

623 声望
105 粉丝
文章目录
宣传栏