original text
https://itnext.io/flutter-sta...
code
https://github.com/iisprey/ri...
reference resources
- https://itnext.io/a-minimalis...
- https://pub.dev/packages/stat...
- https://iisprey.medium.com/ge...
- https://iisprey.medium.com/ho...
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 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
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;
- Create simple and complex providers and combine them
- Create simple and complex providers and combine them
- Use AsyncValue object and show async value in the UI using when method
- Use the AsyncValue object and use the when method in the UI to display the async value
- Also, create freezed objects for immutable object solution
- 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(); } }
Using freezed and json_serializable creating immutable models
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 learn more about freezed, please check out this article.
https://iisprey.medium.com/ho...
Get value from service
You can think, what is AsyncValue? 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, this example is more meaningful
I'm not the master of riverpod. Just learn and share my experience, so please let us know if you know a better way to use riverpod!
Example Github project
This is the source code.
https://github.com/iisprey/ri...
Thank you for reading
© Cat brother