Flutter通用状态管理插件generic_state_bya2的使用

Flutter通用状态管理插件generic_state_bya2的使用

generic_state_bya2

通用状态管理插件。此包消除了类型转换的麻烦,并具有处理API、分页响应或您自己的用例的方法和类。

注意事项

根据项目的不同需求和设置,GenericState总是有所不同的。因此,为了完全自定义此包,我建议克隆GitHub仓库并根据您的需求进行编辑。如果您这样做,您可以更好地自定义加载指示器、错误图形、无数据图形等。

使用方法

控制器

普通API请求

您可以用以下方式处理普通API请求:

Future<void> getValue({bool isRefresh = false}) async {
  await Future.delayed(Duration.zero);
  if (!isRefresh) {
    state = LoadingState();
  }
  try {
    final repositoryData = await repository.getData();
    state = SuccessState(repositoryData);
    // 或者 state = SuccessState.pagination(...) 如果是分页类型
  } catch (e, s) {
    if (state.showToastInError(isRefresh)) {
      showCustomToast(e.toString());
    } else {
      state = ErrorState(e, s);
    }
  }
}

分页API请求

您可以用以下方式处理分页API请求:

Future<void> fetchValue({
  bool isRefresh = false,
  bool isPagination = false,
}) async {
  if (state.isPaginationLoading) return;
  await Future.delayed(Duration.zero);
  if (state.showLoading(isRefresh, isPagination)) {
    state = LoadingState();
  }
  try {
    if (isPagination) {
      state = state.copyToTogglePaginationLoading(true);
    }
    final repositoryData = await repository.getData(
      pageIndex: state.nextPage(isRefresh),
    );
    state = state.copyOfNextOrRefresh(
      response: repositoryData,
      isRefresh: isRefresh,
      oldPlusNewData: () => [
        ...?state.dataOrNull,
        ...repositoryData.data,
      ],
    );
  } catch (e, s) {
    if (state.showToastInError(isRefresh)) {
      showCustomToast(e.toString());
    } else {
      state = ErrorState(e, s);
    }
  }
}

对于分页请求,您需要将存储库数据的返回类型设置为PaginationResponse

辅助方法

此通用状态最好的部分在于它提供了许多辅助方法,这些方法可以消除类型转换的麻烦。

在UI中,您需要获取值,有时如果状态不是成功状态则需要提供替代值,有时需要映射值。

final state = SuccessState(1);
final data = state.dataOrNull;
final data2 = state.dataOr(5);
final data3 = state.dataInKOr<String>(
  onData: (data) => data.toString(),
  alternative: "NULL",
);
final data4 = state.when<int>(
  success: (state) => state.data * 2,
  error: (state) => state.error.hashCode,
  loading: () => 0,
);

以上是该包提供的辅助方法。 您可以在场景中使用这些方法,例如在应用程序头部的用户名。当成功时,您可能需要设置用户名,但当状态正在加载或其他状态时,您想向用户显示“Loading…”文本。因此,您可以这样做:

Text(
  state.dataOrNull?.userName ?? "Loading...",
  style: TextStyle(
      fontSize: 16,
      color: Colors.black,
      fontWeight: FontWeight.bold,
  ),
),

或者,您可以在成功、错误和加载时设置不同的值:

Text(
  state.when(
    success: (state) => state.data.fullName,
    error: (state) => "-",
    loading: () => "Loading...",
  ),
  style: TextStyle(
      fontSize: 16,
      color: Colors.black,
      fontWeight: FontWeight.bold,
  ),
),

最后,它还提供了状态检查方法,如:

state.isLoading;
state.isNotLoading;
state.isSuccess;
state.isNotSuccess;
state.isError;
state.isNotError;

这些方法可以在以下情况下使用:

#1
IgnorePointer(
  ignoring: state.isLoading,
  ...
)

#2
Future<void> mayFetchOrUseCache() async {
  if (state.isNotSuccess) {
    await fetchTheData();
  }
}

#3
if (state.isLoading) {
  return LinearProgressIndicator(
    ...
  );
} else {
  return SizedBox(
    ...
  );
} 

GenericStateWidget 和 GenericStatePaginationWidget

GenericStateWidgetGenericStatePaginationWidget 在处理应用中的API响应时非常有用。此包处理了开发人员在集成API响应时需要考虑的所有复杂情况。

GenericStateWidget(
  state: ref.watch(homeProvider),
  onSuccess: (state) => Column(
    ...
  ),
  onErrorReload: () async {
    ref.read(homeProvider.notifier).loadData();
  },
  isEmptyCheck: (state) => state.data.isEmpty,
  onRefresh: () async {
    await ref.read(homeProvider.notifier).loadData(isRefresh: true);
  },
  loadingShimmer: () => const HomeLoadingShimmer(),
),

最有趣的部分是分页功能。通过此包,您无需担心分页的复杂性。此包处理了滚动控制器的监听, 在需要分页时调用合适的方法,并显示分页加载。

final ScrollController scrollController = ScrollController();

@override
Widget build(BuildContext context) {
  final state = ref.watch(friendListProvider(widget.type));
  return GenericStatePaginationWidget(
    state: state,
    scrollController: scrollController,
    onRefresh: () async {
      await ref
          .read(friendListProvider(widget.type).notifier)
          .getFriends(ref, isRefresh: true);
    },
    isEmptyCheck: (state) => state.data.isEmpty,
    onSuccess: (state) {
      return ListView.builder(
        controller: scrollController,
        itemCount: state.data.length,
        itemBuilder: (context, index) {
          return IndividualFriendWidget(
            item: state.data[index],
          );
        },
      );
    },
    toFetchNextPage: () {
      ref
          .read(friendListProvider(widget.type).notifier)
          .getFriends(ref, isPagination: true);
    },
    onErrorReload: () async {
      ref
          .read(friendListProvider(widget.type).notifier)
          .getFriends(ref);
    },
    loadingShimmer: () => const FriendsLoadingShimmer(),
  );
}

@override
void dispose() {
  scrollController.dispose();
  super.dispose();
}

请注意以下几点:

  • 确保您已将ScrollController传递到可滚动的UI部分。
  • 释放ScrollController。重复一遍,释放ScrollController。不这样做不会导致包或应用程序出现问题,但它会导致内存泄漏,而一个好的程序员总是会处理这种情况。

有时GenericStateWidgetGenericStatePaginationWidgetCustomScrollView 中,这意味着父级期望子元素是一个Sliver而不是普通的RenderBox

在这种情况下,您可以传递isSliver: true,默认情况下它是false

GenericStatePaginationWidget(
  isSliver: true,
  ...
),

请注意以下几点:

  • 如果isSlivertrue,您传递的onRefresh代码永远不会运行。因为您需要在父类中处理onRefresh,即CustomScrollView
  • 类似地,与普通的分页小部件不同,当isSlivertrue时,底部加载指示器在加载下一页时将无法工作,您需要手动在ListView的最后一项上设置加载。
GenericStatePaginationWidget(
  onSuccess: (state) {
    return SliverList(
      delegate: SliverChildBuilderDelegate(
        (_, int index) {
          final bool showPaginationLoading = state.isPaginationLoading && index == state.data.length - 1;
          return IndividualFriendWidget(
            item: state.data[index],
            showPaginationLoading: showPaginationLoading,
          );
        },
        childCount: state.data.length,
      ),
    );
  },
  ...
),

// IndividualFriendWidget
Column(
  children: [
    ...
    if (showPaginationLoading)
      const Center(child: CircularProgressIndicator()),
  ],
)

PaginationResponse

多亏了PaginationResponse,我们的包可以确定页面是否有下一页,从而相应地加载下一页或不加载。并且在分页方面帮助了许多其他设置。

// 设置PaginationResponse,通常在主函数中,如果不这样做,将使用默认值。
PaginationResponseSetup.setup(
  haveNext: (response, pageNumber) {
    return response["totalPages"] > pageNumber;
  },
  paramsMap: (pageIndex, pageSize) {
    return {
      "pageIndex": pageIndex,
      "pageSize": pageSize,
    };
  },
  pageSize: 20,
);

// 在您的存储库中返回PaginationResponse。
class FriendsRepository extends BaseRepository {
  Future<PaginationResponse<List<Friend>>> getFriendList({required int pageIndex}) async {
    return await get<PaginationResponse<List<Friend>>>(
      RequestInput(
        url: ApiConstants.getAllFriends,
        params: PaginationResponse.params(pageIndex), // PaginationResponse类提供的参数
        body: null,
        parseJson: (response) {
          return PaginationResponse( // 在API获取和JSON解析后返回PaginationResponse
            data: (response["data"] as List)
                .map((e) => Friend.fromJson(e))
                .toList(),
            response: response,
            pageIndex: pageIndex,
          );
        },
      ),
    );
  }

  // 并像这样使用它
  final response = await FriendsRepository().getFriendList(pageIndex: 1);
  responseData.data;
  responseData.haveNext;
  responseData.pageIndex;
  responseData.oldPlusNew([
    ...?state.dataOrNull, // 旧数据
    ...responseData.data, // 新数据
  ]);
  // 并在控制器中发出状态以进行分页
  emit(SuccessState.pagination(...)); // BLOC
  state = SuccessState.pagination(...); // Riverpod
}

更多关于Flutter通用状态管理插件generic_state_bya2的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter通用状态管理插件generic_state_bya2的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用generic_state_bya2插件进行通用状态管理的代码示例。这个插件提供了一种灵活的方式来管理应用的状态,通过使用泛型来减少样板代码。

首先,确保你已经在pubspec.yaml文件中添加了generic_state_bya2依赖:

dependencies:
  flutter:
    sdk: flutter
  generic_state_bya2: ^最新版本号  # 请替换为实际的最新版本号

然后运行flutter pub get来安装依赖。

使用示例

  1. 定义状态类型

首先,定义你的应用可能需要的状态类型。例如,一个简单的加载状态和数据状态:

enum AppStateStatus { loading, success, error }

class AppState<T> {
  final AppStateStatus status;
  final T? data;
  final String? errorMessage;

  AppState({
    required this.status,
    this.data,
    this.errorMessage,
  });

  AppState.loading() : this(status: AppStateStatus.loading);

  AppState.success(T data) : this(status: AppStateStatus.success, data: data);

  AppState.error(String errorMessage)
      : this(status: AppStateStatus.error, errorMessage: errorMessage);
}
  1. 创建ViewModel

接下来,创建一个ViewModel来处理状态逻辑。使用GenericState来管理状态:

import 'package:generic_state_bya2/generic_state.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final appStateProvider = StateNotifierProvider<AppStateNotifier, AppState<List<String>>>((ref) {
  return AppStateNotifier();
});

class AppStateNotifier extends StateNotifier<AppState<List<String>>> {
  AppStateNotifier() : super(AppState.loading());

  void fetchData() async {
    // 模拟一个异步数据获取过程
    await Future.delayed(Duration(seconds: 2));
    try {
      final data = ['Item 1', 'Item 2', 'Item 3'];
      state = AppState.success(data);
    } catch (e) {
      state = AppState.error('Failed to fetch data');
    }
  }
}

注意:这里我们使用了flutter_riverpod来管理Provider,但generic_state_bya2可以与任何状态管理库一起使用,只要你能获取和更新状态。

  1. 在UI中使用状态

最后,在你的UI组件中使用这个状态:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'your_state_and_viewmodel_files.dart'; // 引入你定义的状态和ViewModel文件

void main() {
  runApp(
    ProviderContainer(
      overrides: [
        appStateProvider.overrideWithValue(AppStateNotifier()),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Generic State Management')),
        body: Consumer(
          builder: (context, ref, widget) {
            final state = ref.watch(appStateProvider);

            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  if (state.status == AppStateStatus.loading)
                    CircularProgressIndicator(),
                  if (state.status == AppStateStatus.success)
                    ListView.builder(
                      itemCount: state.data!.length,
                      itemBuilder: (context, index) {
                        return ListTile(title: Text(state.data![index]));
                      },
                    ),
                  if (state.status == AppStateStatus.error)
                    Text('Error: ${state.errorMessage!}'),
                ],
              ),
            );
          },
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => ref.read(appStateProvider.notifier).fetchData(),
          tooltip: 'Fetch Data',
          child: Icon(Icons.refresh),
        ),
      ),
    );
  }
}

在这个示例中,我们创建了一个简单的Flutter应用,它使用generic_state_bya2flutter_riverpod来管理应用的状态。当点击浮动按钮时,应用会模拟一个异步数据获取过程,并更新UI以反映当前的状态(加载中、成功或错误)。

请根据你的实际需求调整状态类型、ViewModel和UI组件。

回到顶部