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

Flutter健身App中Future<List<WorkoutData>>类型错误及map调用问题

问题分析

开发Flutter健身App时,遇到两个核心问题:

  1. 从Firestore获取周数、天数是异步操作,导致初始化训练列表的函数返回Future<List<WorkoutData>>,无法直接赋值给Bloc中List<WorkoutData>类型的变量,触发类型不匹配错误。
  2. 若将Bloc的workouts改为Future<List<WorkoutData>>类型,UI组件中调用map方法时会报错,因为Future没有map方法。
解决方案

1. 重构Bloc逻辑:将异步操作移至事件处理中

Bloc的核心是通过状态管理数据,不应在类成员变量中直接存储Future。正确做法是在初始化事件中完成异步数据获取,再将结果封装到状态中发送给UI。

修改后的Bloc代码:

class WorkoutsBloc extends Bloc<WorkoutsEvent, WorkoutsState> {
  WorkoutsBloc() : super(WorkoutsInitial()) {
    // 新版Bloc推荐使用on绑定事件处理
    on<WorkoutsInitialEvent>(_handleInitialLoad);
  }

  // 处理初始化事件的异步逻辑
  Future<void> _handleInitialLoad(WorkoutsInitialEvent event, Emitter<WorkoutsState> emit) async {
    // 获取用户的周、天数据
    final week = await getWeek();
    final day = await getDay();
    
    // 初始化基础训练列表
    List<WorkoutData> workouts = DataConstants.workouts
        .where((element) => element.id!.contains("Week${week}Day${day}"))
        .toList();
    workouts.addAll(DataConstants.homeWorkouts);

    // 合并用户自定义训练数据
    GlobalConstants.workouts = await DataService.getWorkoutsForUser();
    for (int i = 0; i < workouts.length; i++) {
      final userWorkoutIndex = GlobalConstants.workouts.indexWhere((w) => w.id == workouts[i].id);
      if (userWorkoutIndex != -1) {
        workouts[i] = GlobalConstants.workouts[userWorkoutIndex];
      }
    }

    // 发送包含训练数据的状态
    emit(ReloadWorkoutsState(workouts: workouts));
  }

  // 兼容旧版Bloc的mapEventToState写法(可选)
  @override
  Stream<WorkoutsState> mapEventToState(WorkoutsEvent event,) async* {
    if (event is WorkoutsInitialEvent) {
      final week = await getWeek();
      final day = await getDay();
      List<WorkoutData> workouts = DataConstants.workouts
          .where((element) => element.id!.contains("Week${week}Day${day}"))
          .toList();
      workouts.addAll(DataConstants.homeWorkouts);

      GlobalConstants.workouts = await DataService.getWorkoutsForUser();
      for (int i = 0; i < workouts.length; i++) {
        final userWorkoutIndex = GlobalConstants.workouts.indexWhere((w) => w.id == workouts[i].id);
        if (userWorkoutIndex != -1) {
          workouts[i] = GlobalConstants.workouts[userWorkoutIndex];
        }
      }
      yield ReloadWorkoutsState(workouts: workouts);
    } else if (event is CardTappedEvent) {
      yield CardTappedState(workout: event.workout);
    }
  }
}

// 简化getWeek和getDay的冗余异步逻辑
Future<String?> getWeek() async {
  final auth = FirebaseAuth.instance;
  final userId = auth.currentUser?.uid;
  if (userId == null) return null;
  
  final docSnapshot = await FirebaseFirestore.instance.collection("users").doc(userId).get();
  return docSnapshot.data()?["weekNumber"].toString();
}

Future<String?> getDay() async {
  final auth = FirebaseAuth.instance;
  final userId = auth.currentUser?.uid;
  if (userId == null) return null;
  
  final docSnapshot = await FirebaseFirestore.instance.collection("users").doc(userId).get();
  return docSnapshot.data()?["dayInWeek"].toString();
}

2. 修改UI组件:从Bloc状态中获取同步数据

UI组件不应直接访问Bloc的成员变量,而是通过BlocBuilder监听状态变化,从状态中获取已处理好的同步训练列表。

修改后的WorkoutContent代码:

class WorkoutContent extends StatelessWidget {
  WorkoutContent({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: ColorConstants.homeBackgroundColor,
      height: double.infinity,
      width: double.infinity,
      child: _createHomeBody(context),
    );
  }

  Widget _createHomeBody(BuildContext context) {
    return BlocBuilder<WorkoutsBloc, WorkoutsState>(
      buildWhen: (_, currState) => currState is ReloadWorkoutsState,
      builder: (context, state) {
        // 初始状态下触发初始化事件并显示加载
        if (state is WorkoutsInitial) {
          context.read<WorkoutsBloc>().add(WorkoutsInitialEvent());
          return const Center(child: CircularProgressIndicator());
        }
        
        // 从状态中取出训练列表
        final workouts = (state as ReloadWorkoutsState).workouts;
        return Padding(
          padding: const EdgeInsets.only(top: 50),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 20.0),
                child: Text(
                  TextConstants.workouts,
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
              ),
              const SizedBox(height: 5),
              Expanded(
                child: ListView(
                  children: workouts.map((e) => _createWorkoutCard(e)).toList(),
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  Widget _createWorkoutCard(WorkoutData workoutData) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 20),
      child: WorkoutCard(workout: workoutData),
    );
  }
}

关键注意点

  • Bloc的核心是状态驱动UI,所有异步操作都应在事件处理中完成,结果通过状态传递,避免在Bloc类中存储Future类型变量。
  • UI组件通过BlocBuilder监听状态变化,从状态中获取同步数据,而非直接访问Bloc成员变量。
  • 简化了getWeek和getDay的冗余代码:data?["xxx"].toString()不是异步操作,无需加await;async函数会自动将返回值包装为Future,无需手动返回Future.value()。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 09:09:57