原文
https://itnext.io/flutter-sta...
程式碼
https://github.com/iisprey/ri...
參考
- https://itnext.io/a-minimalis...
- https://pub.dev/packages/stat...
- https://iisprey.medium.com/ge...
- https://iisprey.medium.com/ho...
正文
正如我上週所承諾的,我將向您展示我自己的最終國家管理解決方案路徑
Riverpod + StateNotifier + Hooks + Freezed
Riverpod 太棒了!但是好的例子並不多。只有最基本的,就這樣。這一次,我試圖使一個例子既可理解又複雜。我的目的是通過這個例子教你什麼時候使用 Riverpod,以及如何使用它。儘管我簡化了過程。希望你喜歡!
動機
在這個例子中我們要做什麼?
我們只需要從 API 中獲取一些資料,然後在 UI 中對它們進行排序和過濾
基本上,我們會;
- Create simple and complex providers and combine them
- 建立簡單和複雜的提供程式並將它們組合起來
- Use AsyncValue object and show async value in the UI using
when
method - 使用 AsyncValue 物件並在 UI 中使用 when 方法顯示 async 值
- Also, create
freezed
objects for immutable object solution - 同時,為不可變物件/解決方案建立凍結物件
我們開始吧!
建立 API 服務
注意: 我沒有找到一個好的 API 模型來使用過濾功能,因為我自己新增了這些角色。原諒我這麼說
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();
}
}
使用 freezed
和 json_serializable
建立不可變模型
我們只需要建立一個
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);
}
如果你想了解更多關於 freezed 的資訊,請檢視這篇文章。
https://iisprey.medium.com/ho...
從服務中獲得 value
你可以想想,AsyncValue
是什麼? 它只是一個聯合類,幫助我們處理我們的值的狀態。它提供了一個現成的提供者包。
我將在接下來的文章中詳細解釋,但就目前而言,僅此而已。
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);
}
}
建立排序和過濾器提供程式
enum Role { none, normal, admin }
enum Sort { normal, reversed }
final filterProvider = StateProvider.autoDispose<Role>((_) => Role.none);
final sortProvider = StateProvider.autoDispose<Sort>((_) => Sort.normal);
從提供程式獲取獲取的列表並進行篩選,然後使用其他提供程式對它們進行排序
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;
}
});
在使用者介面中顯示所有內容
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',
),
);
},
),
),
),
],
),
);
}
}
結束
如果你把這個例子看作一個電子商務應用程式,那麼這個例子就更有意義了
我不是 riverpod
的宗師。只是學習和分享我的經驗,所以請如果你知道一個更好的方式使用 riverpod
請讓我們知道!
示例 Github 專案
這是原始碼。
https://github.com/iisprey/ri...
謝謝你的閱讀
© 貓哥