基于 Riverpod 的 Flutter 状态管理

会煮咖啡的猫
English

原文

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

代码

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

参考

正文

正如我上周所承诺的,我将向您展示我自己的最终国家管理解决方案路径

Riverpod + StateNotifier + Hooks + Freezed

Riverpod 太棒了!但是好的例子并不多。只有最基本的,就这样。这一次,我试图使一个例子既可理解又复杂。我的目的是通过这个例子教你什么时候使用 Riverpod,以及如何使用它。尽管我简化了过程。希望你喜欢!

动机

在这个例子中我们要做什么?

我们只需要从 API 中获取一些数据,然后在 UI 中对它们进行排序和过滤

基本上,我们会;

  1. Create simple and complex providers and combine them
  2. 创建简单和复杂的提供程序并将它们组合起来
  3. Use AsyncValue object and show async value in the UI using when method
  4. 使用 AsyncValue 对象并在 UI 中使用 when 方法显示 async 值
  5. Also, create freezed objects for immutable object solution
  6. 同时,为不可变物件/解决方案创建冻结对象

我们开始吧!

创建 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();
  }
}

使用 freezedjson_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...

谢谢你的阅读


© 猫哥

订阅号

阅读 717

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

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

623 声望
106 粉丝
0 条评论

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

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