基於 Riverpod 的 Flutter 狀態管理

會煮咖啡的貓發表於2022-01-14

原文

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...

謝謝你的閱讀


© 貓哥

訂閱號

相關文章