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

Flutter健身APP创建锻炼集合后数据重复展示异常求助

问题:锻炼集合数据重复显示,新分类无法正确展示

我用Flutter开发了一款健身APP,现在遇到一个问题——创建的每个锻炼集合都会显示相同的数据。在个人资料页面创建新的锻炼分类后,页面没有展示对应的新数据,而是依然显示重复内容。

以下是锻炼日程页面的核心代码:

class WorkoutContentAdded extends StatefulWidget {
  final String programId;

  const WorkoutContentAdded({Key? key, required this.programId}) : super(key: key);

  @override
  _WorkoutContentAddedState createState() => _WorkoutContentAddedState();
}

class _WorkoutContentAddedState extends State<WorkoutContentAdded> {
  late Stream<QuerySnapshot> _workoutScheduleStream;

  @override
  void initState() {
    super.initState();
    _initializeWorkoutScheduleStream();
  }

  @override
  void didUpdateWidget(WorkoutContentAdded oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.programId != widget.programId) {
      _initializeWorkoutScheduleStream();
    }
  }

  void _initializeWorkoutScheduleStream() {
    print('Initializing workout schedule stream for program: ${widget.programId}');
    _workoutScheduleStream = FirebaseFirestore.instance
        .collection('programs')
        .doc(widget.programId)
        .collection('workout_schedule')
        .orderBy('order')
        .snapshots();
  }

  @override
  Widget build(BuildContext context) {
    print('Building WorkoutContentAdded for program: ${widget.programId}');
    return Scaffold(
      // ... other scaffold properties
      body: CustomScrollView(
        slivers: [
          // ... other slivers
          SliverToBoxAdapter(
            child: Padding(
              padding: EdgeInsets.fromLTRB(16, 16, 16, 0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Workout Schedule',
                    style: TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  StreamBuilder<QuerySnapshot>(
                    stream: _workoutScheduleStream,
                    builder: (context, scheduleSnapshot) {
                      print('Workout schedule stream update for program: ${widget.programId}');
                      print('Connection state: ${scheduleSnapshot.connectionState}');
                      if (scheduleSnapshot.hasError) {
                        print('Error in schedule stream: ${scheduleSnapshot.error}');
                      }
                      if (scheduleSnapshot.hasData) {
                        print('Number of workout days: ${scheduleSnapshot.data!.docs.length}');
                      }

                      if (scheduleSnapshot.connectionState == ConnectionState.waiting) {
                        return CircularProgressIndicator();
                      }

                      if (scheduleSnapshot.hasError) {
                        return Text('Error: ${scheduleSnapshot.error}');
                      }

                      if (!scheduleSnapshot.hasData || scheduleSnapshot.data!.docs.isEmpty) {
                        return Padding(
                          padding: EdgeInsets.only(top: 16),
                          child: Text('No workout schedule added for this program.'),
                        );
                      }

                      List<DocumentSnapshot> sortedDocs = scheduleSnapshot.data!.docs;

                      return ListView.builder(
                        shrinkWrap: true,
                        physics: NeverScrollableScrollPhysics(),
                        itemCount: sortedDocs.length,
                        padding: EdgeInsets.only(top: 10),
                        itemBuilder: (context, index) {
                          Map<String, dynamic> data = sortedDocs[index].data() as Map<String, dynamic>;
                          String day = data['day'] ?? 'Unknown Day';
                          return GestureDetector(
                            onTap: () {
                              Navigator.push(
                                context,
                                MaterialPageRoute(
                                  builder: (context) => VideoContent(
                                    day: day,
                                    allowEditing: true,
                                    programId: widget.programId,
                                  ),
                                ),
                              );
                            },
                            child: Card(
                              // Card content
                            ),
                          );
                        },
                      );
                    },
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _showAddCategoryBottomSheet,
        backgroundColor: Colors.black,
        child: Icon(Icons.add, color: Colors.white),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
    );
  }

  void _showAddCategoryBottomSheet() {
    String selectedOrdinal = '1st';
    String selectedDay = 'Monday';
    String estimatedKcal = '';

    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      backgroundColor: Colors.white,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
      ),
      builder: (BuildContext context) {
        return StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Padding(
              padding: EdgeInsets.only(
                bottom: MediaQuery.of(context).viewInsets.bottom,
                left: 24,
                right: 24,
                top: 24,
              ),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Choose the day of the week',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  SizedBox(height: 20),
                  Row(
                    children: [
                      Expanded(
                        child: DropdownButtonFormField<String>(
                          value: selectedOrdinal,
                          decoration: InputDecoration(
                            border: OutlineInputBorder(),
                            contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
                          ),
                          items: ['1st', '2nd', '3rd', '4th']
                              .map((String value) {
                            return DropdownMenuItem<String>(
                              value: value,
                              child: Text(value),
                            );
                          }).toList(),
                          onChanged: (String? newValue) {
                            if (newValue != null) {
                              setState(() {
                                selectedOrdinal = newValue;
                              });
                            }
                          },
                        ),
                      ),
                      SizedBox(width: 10),
                      Expanded(
                        child: DropdownButtonFormField<String>(
                          value: selectedDay,
                          decoration: InputDecoration(
                            border: OutlineInputBorder(),
                            contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
                          ),
                          items: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
                              .map((String value) {
                            return DropdownMenuItem<String>(
                              value: value,
                              child: Text(value),
                            );
                          }).toList(),
                          onChanged: (String? newValue) {
                            if (newValue != null) {
                              setState(() {
                                selectedDay = newValue;
                              });
                            }
                          },
                        ),
                      ),
                    ],
                  ),
                  SizedBox(height: 20),
                  TextField(
                    decoration: InputDecoration(
                      hintText: 'Estimative kcal burnt',
                      border: OutlineInputBorder(),
                    ),
                    keyboardType: TextInputType.number,
                    onChanged: (value) {
                      estimatedKcal = value;
                    },
                  ),
                  SizedBox(height: 20),
                  Center(
                    child: ElevatedButton(
                      onPressed: () async {
                        await FirebaseFirestore.instance.collection('workout_schedule').add({
                          'ordinal': selectedOrdinal,
                          'day': selectedDay,
                          'estimatedKcal': estimatedKcal,
                        });
                        Navigator.pop(context);
                      },
                      child: Text('Save'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.black,
                        foregroundColor: Colors.white,
                        padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
                        shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(30),
                        ),
                      ),
                    ),
                  ),
                  SizedBox(height: 20),
                ],
              ),
            );
          }
        );
      },
    );
  }
}

解决方案

1. 修复数据存储路径错误

代码中存在核心问题:读取和保存数据的路径不匹配。

  • 读取数据时,从programs/{programId}/workout_schedule子集合获取;
  • 保存数据时,直接存入根集合workout_schedule,导致所有program读取的都是同一批数据,因此显示重复。

修改_showAddCategoryBottomSheet中的保存逻辑,将数据存入对应program的子集合:

// 替换原有的保存代码
await FirebaseFirestore.instance
    .collection('programs')
    .doc(widget.programId) // 关联当前program的ID
    .collection('workout_schedule')
    .add({
      'ordinal': selectedOrdinal,
      'day': selectedDay,
      'estimatedKcal': estimatedKcal,
      'order': _getOrderFromOrdinal(selectedOrdinal), // 补充排序用的order字段
    });

同时添加辅助函数,将ordinal转换为数值类型的order(读取时用了orderBy('order'),原代码缺少该字段会导致排序异常):

int _getOrderFromOrdinal(String ordinal) {
  switch(ordinal) {
    case '1st': return 1;
    case '2nd': return 2;
    case '3rd': return 3;
    case '4th': return 4;
    default: return 0;
  }
}

2. 验证Stream更新逻辑

当前代码的didUpdateWidget已经处理了programId变化时重新初始化Stream的逻辑,这部分是正确的。可以通过控制台打印的日志,确认programId是否正确切换,确保Stream指向对应program的子集合。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 00:13:10