You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Bloc如何实现初始拉取全量数据后按需发射不同状态?

核心结论

首先明确存储位置选择:和当前页面生命周期绑定、仅在当前页面交互流程中使用的预拉取全量数据,统一存储在Cubit对应的状态类中,不要存储在Repository层。
Repository的核心职责是抽象数据源(API、本地DB、缓存),只负责提供数据读写能力,不应该持有和单页面交互状态绑定的临时数据,否则多页面实例同时调用Repository时会出现数据污染、缓存过期不更新的问题。

具体实现方案

按照flutter_bloc + Repository的分层规范,实现逻辑拆分为三层:

1. Repository层实现

Repository只做一件事:聚合拉取页面初始化需要的全量数据,不处理任何UI交互相关的状态逻辑。

// 全量数据的实体类,根据业务自己定义字段
class StorePageFullData {
  final List<Store> stores;
  final List<Route> drivingRoutes;
  final List<Route> walkingRoutes;

  StorePageFullData({
    required this.stores,
    required this.drivingRoutes,
    required this.walkingRoutes,
  });
}

class StoreRepository {
  Future<StorePageFullData> fetchAllPageData(Location userLocation) async {
    // 用Future.wait并行拉取,缩短加载时间
    final results = await Future.wait([
      _api.getStoreList(),
      _api.getDrivingRoutes(userLocation: userLocation),
      _api.getWalkingRoutes(userLocation: userLocation),
    ]);
    return StorePageFullData(
      stores: results[0] as List<Store>,
      drivingRoutes: results[1] as List<Route>,
      walkingRoutes: results[2] as List<Route>,
    );
  }
}

2. Cubit与状态层实现

状态类持有全量数据,同时维护当前UI需要展示的路由类型标记;Cubit在初始化时自动触发全量数据拉取,提供切换路线的方法,不需要重复请求接口。

// 路线类型枚举
enum RouteType { driving, walking }

// 基类状态
sealed class StoreMapState extends Equatable {
  const StoreMapState();
}

class StoreMapLoading extends StoreMapState {
  @override
  List<Object?> get props => [];
}

class StoreMapLoadFailed extends StoreMapState {
  final String errorMsg;
  const StoreMapLoadFailed(this.errorMsg);

  @override
  List<Object?> get props => [errorMsg];
}

// 加载完成的状态,持有全量数据+当前展示标记
class StoreMapLoaded extends StoreMapState {
  final List<Store> stores;
  final List<Route> drivingRoutes;
  final List<Route> walkingRoutes;
  final RouteType currentRouteType;

  const StoreMapLoaded({
    required this.stores,
    required this.drivingRoutes,
    required this.walkingRoutes,
    this.currentRouteType = RouteType.driving,
  });

  // copyWith方法方便切换状态时复用已有全量数据
  StoreMapLoaded copyWith({RouteType? currentRouteType}) {
    return StoreMapLoaded(
      stores: stores,
      drivingRoutes: drivingRoutes,
      walkingRoutes: walkingRoutes,
      currentRouteType: currentRouteType ?? this.currentRouteType,
    );
  }

  @override
  List<Object?> get props => [stores, drivingRoutes, walkingRoutes, currentRouteType];
}

// Cubit实现
class StoreMapCubit extends Cubit<StoreMapState> {
  final StoreRepository _repository;
  final Location userLocation;

  StoreMapCubit({
    required StoreRepository repository,
    required this.userLocation,
  }) : _repository = repository,
       super(StoreMapLoading()) {
    // 初始化时直接触发全量数据拉取
    _initLoad();
  }

  Future<void> _initLoad() async {
    try {
      final fullData = await _repository.fetchAllPageData(userLocation);
      // 首次加载完成默认展示驾车路线,全量数据随状态一起发出
      emit(StoreMapLoaded(
        stores: fullData.stores,
        drivingRoutes: fullData.drivingRoutes,
        walkingRoutes: fullData.walkingRoutes,
      ));
    } catch (e) {
      emit(StoreMapLoadFailed(e.toString()));
    }
  }

  // 切换路线方法,不需要重新请求,直接复用已有数据发新状态
  void switchRoute(RouteType targetType) {
    final curState = state;
    if (curState is! StoreMapLoaded || curState.currentRouteType == targetType) return;
    emit(curState.copyWith(currentRouteType: targetType));
  }
}

3. 页面层实现

  • 页面初始化时通过BlocProvider创建StoreMapCubit实例,传入当前用户位置和Repository实例
  • 用BlocBuilder监听状态变化:
    • 收到StoreMapLoading时展示加载进度条
    • 收到StoreMapLoadFailed时展示错误提示和重试按钮
    • 收到StoreMapLoaded时,根据currentRouteType取对应的路线数据传给地图组件渲染
  • 路线切换按钮点击时,调用context.read<StoreMapCubit>().switchRoute(对应路线类型)即可触发UI更新,无网络请求开销
注意事项
  • 如果预拉取的数据需要跨多个页面复用、数据体量较大,可以在Repository层做带唯一标识的缓存(比如以用户位置+城市编码为key),但单页面的Cubit仍然需要持有当前页面使用的数据副本,避免多页面操作导致Repository缓存变化引发当前页面状态异常
  • 不要在Cubit类的私有字段里存全量数据,所有UI渲染需要的数据都要放在状态类中,否则切后台、组件重建时会出现数据丢失、UI和状态不一致的问题
  • 全量拉取时建议用Future.wait并行请求多个接口,比串行请求速度快很多,减少用户等待时间

内容的提问来源于stack exchange,提问作者Mary

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 09:42:24