original
https://itnext.io/flutter-state-management-with-riverpod-ef8d4ef77392
code
https://github.com/iisprey/riverpod_example
refer to
- https://itnext.io/a-minimalist-guide-to-riverpod-4eb24b3386a1
- https://pub.dev/packages/state_notifier
- https://iisprey.medium.com/get-rid-of-all-kind-of-boilerplate-code-with-flutter-hooks-2e17eea06ca0
- https://iisprey.medium.com/how-to-handle-complex-json-in-flutter-4982015b4fdf
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;
- 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 display the async value in the UI using the when method
- Also, create
freezed
objects for immutable object solution - 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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。