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

Flutter动态页面创建与Hive数据隔离问题求助

问题描述

我做了一个班级列表页面,用户点击按钮输入名称就能创建新班级并显示在ListView里;点击班级会打开对应名称的学生列表页面,在里面可以创建学生列表,数据用Hive存储。

现在遇到的问题:在某个班级的学生列表页面创建学生后,返回班级列表再点其他班级,所有班级页面都会显示之前创建的学生列表。

问题原因:学生列表页面是同一个语法类,所有班级都会复用它。但用户可以无限创建班级,没法给每个班级都单独写一个类,该怎么解决?


班级列表页面代码

import 'package:attendance/data/database.dart';
import 'package:flutter/material.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:attendance/insideList.dart';
import 'package:hive_flutter/hive_flutter.dart';

class lists extends StatefulWidget {
  const lists({super.key});

  @override
  State<lists> createState() => _listsState();
}

class _listsState extends State<lists> {
  final _myBox = Hive.box('mybox');

  ListDataBase db = ListDataBase();

  late TextEditingController _textController;
  @override
  void initState() {
    if (_myBox.get("NAMES") == null) {
      db.InitialData();
    } else {
      db.LoadData();
    }
    super.initState();
    _textController = TextEditingController();
  }

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

  @override
  Widget build(BuildContext context) {
    db.Items.sort();
    return Scaffold(
      body: db.Items.length > 0
          ? ListView.separated(
              itemCount: db.Items.length,
              itemBuilder: (_, index) {
                return ListTile(
                  leading: const Icon(Icons.school),
                  trailing: const Icon(Icons.arrow_forward),
                  title: Center(child: Text(db.Items[index])),
                  onTap: () {
                    Navigator.push(
                        context,
                        MaterialPageRoute(
                            builder: ((context) =>
                                InsideList(db.Items[index]))));
                  },
                  onLongPress: (() async {
                    await showDialog(
                        context: context,
                        builder: ((context) {
                          return AlertDialog(
                            title: const Text(
                              "Are you sure you want to delete this class?",
                              style: TextStyle(fontSize: 15),
                            ),
                            actions: [
                              TextButton(
                                  child: Text("cancel"),
                                  onPressed: (() {
                                    Navigator.pop(context);
                                  })),
                              TextButton(
                                child: Text('Delete'),
                                onPressed: () {
                                  setState(() {
                                    db.Items.removeAt(index);
                                    db.UpdateDataBase();
                                    Navigator.pop(context);
                                  });
                                },
                              ),
                            ],
                          );
                        }));
                  }),
                );
              },
              separatorBuilder: (BuildContext context, int index) =>
                  const Divider(
                color: Colors.black,
              ),
            )
          : const Center(
              child: Text("You currently have no classes. Add from below."),
            ),
      floatingActionButton: SpeedDial(
        animatedIcon: AnimatedIcons.menu_arrow,
        spacing: 6,
        spaceBetweenChildren: 6,
        backgroundColor: const Color.fromARGB(255, 22, 37, 50),
        foregroundColor: const Color.fromARGB(255, 255, 255, 255),
        children: [
          SpeedDialChild(
            child: const Icon(Icons.school),
            label: "add class",
            onTap: () async {
              final result = await showDialog(
                context: context,
                builder: (context) {
                  return AlertDialog(
                    title: const Text('Add a new class'),
                    content: TextField(
                      controller: _textController,
                      autofocus: true,
                      decoration: const InputDecoration(
                          hintText: "Enter the name of the class."),
                    ),
                    actions: [
                      TextButton(
                        child: Text('Cancel'),
                        onPressed: () {
                          Navigator.pop(context);
                        },
                      ),
                      TextButton(
                        child: Text('Add'),
                        onPressed: () {
                          Navigator.pop(context, _textController.text);
                          db.UpdateDataBase();
                          _textController.clear();
                        },
                      ),
                    ],
                  );
                },
              );
              if (result != null) {
                result as String;
                setState(() {
                  db.Items.add(result);
                });
              }
            },
          )
        ],
      ),
    );
  }
}

学生列表页面代码

import 'package:attendance/data/StudentsDatabase.dart';
import 'package:flutter/material.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:hive_flutter/hive_flutter.dart';

class InsideList extends StatefulWidget {
  final String name;

  InsideList(this.name);

  @override
  State<InsideList> createState() => _InsideListState();
}

class _InsideListState extends State<InsideList> {
  final _myBox = Hive.box('mybox2');

  StudentsDatabase db = StudentsDatabase();

  late TextEditingController _textController;

  @override
  void initState() {
    if (_myBox.get("NAMES") == null) {
      db.InitialData();
    } else {
      db.LoadData();
    }
    super.initState();
    _textController = TextEditingController();
  }

  void _selectRadio(int index, int? val) {
    setState(() {
      db.SelectedRadio[index] = val ?? 0;
    });
  }

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

  Widget build(BuildContext context) {
    db.Students.sort();
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.name),
        centerTitle: true,
        backgroundColor: const Color.fromARGB(255, 22, 37, 50),
        toolbarHeight: 65,
        shape: const RoundedRectangleBorder(
          borderRadius: BorderRadius.vertical(
            bottom: Radius.circular(30),
          ),
        ),
      ),
      body: db.Students.length > 0
          ? ListView.separated(
              itemCount: db.Students.length,
              itemBuilder: (_, index) {
                return ListTile(
                  leading: const Icon(Icons.person),
                  trailing: FittedBox(
                    fit: BoxFit.fill,
                    child: Row(
                      children: [
                        Radio(
                            activeColor: Colors.green,
                            value: 0,
                            groupValue: db.SelectedRadio[index],
                            onChanged: (val) {
                              _selectRadio(index, val);
                              db.UpdateDataBase();
                            }),
                        Radio(
                            activeColor: Colors.red,
                            value: 1,
                            groupValue: db.SelectedRadio[index],
                            onChanged: (val) {
                              _selectRadio(index, val);
                              db.UpdateDataBase();
                            }),
                      ],
                    ),
                  ),
                  title: Center(child: Text(db.Students[index])),
                  onTap: () {
                    Navigator.push(
                        context,
                        MaterialPageRoute(
                            builder: ((context) =>
                                InsideList(db.Students[index]))));
                  },
                  onLongPress: (() async {
                    await showDialog(
                        context: context,
                        builder: ((context) {
                          return AlertDialog(
                            title: const Text(
                              "Are you sure you want to delete this student?",
                              style: TextStyle(fontSize: 15),
                            ),
                            actions: [
                              TextButton(
                                  child: Text("cancel"),
                                  onPressed: (() {
                                    Navigator.pop(context);
                                  })),
                              TextButton(
                                child: Text('Delete'),
                                onPressed: () {
                                  setState(() {
                                    db.Students.removeAt(index);
                                    db.UpdateDataBase();
                                    Navigator.pop(context);
                                  });
                                },
                              ),
                            ],
                          );
                        }));
                  }),
                );
              },
              separatorBuilder: (BuildContext context, int index) =>
                  const Divider(
                color: Colors.black,
              ),
            )
          : const Center(
              child: Text("You currently have no students. Add from below."),
            ),
      floatingActionButton: SpeedDial(
        animatedIcon: AnimatedIcons.menu_arrow,
        spacing: 6,
        spaceBetweenChildren: 6,
        backgroundColor: const Color.fromARGB(255, 22, 37, 50),
        foregroundColor: const Color.fromARGB(255, 255, 255, 255),
        children: [
          SpeedDialChild(
            child: const Icon(Icons.group_add),
            label: "add student",
            onTap: () async {
              final result = await showDialog(
                context: context,
                builder: (context) {
                  return AlertDialog(
                    title: const Text('Add a new student'),
                    content: TextField(
                      controller: _textController,
                      autofocus: true,
                      decoration: const InputDecoration(
                          hintText: "Enter the name of the student."),
                    ),
                    actions: [
                      TextButton(
                        child: Text('Cancel'),
                        onPressed: () {
                          Navigator.pop(context);
                        },
                      ),
                      TextButton(
                        child: Text('Add'),
                        onPressed: () {
                          Navigator.pop(context, _textController.text);
                          db.UpdateDataBase();
                          _textController.clear();
                        },
                      ),
                    ],
                  );
                },
              );
              if (result != null) {
                result as String;
                setState(() {
                  db.Students.add(result);
                  db.SelectedRadio.add(0);
                });
              }
            },
          )
        ],
      ),
    );
  }
}

解决方案

核心问题是所有班级共用了同一个学生数据库实例和Hive存储键,导致数据混在一起。解决思路是给每个班级的学生数据分配独立的存储标识,让同一个InsideList类根据传入的班级名称加载对应的数据。

步骤1:修改学生数据库类(StudentsDatabase)

让它接收班级名称作为参数,根据班级名称生成唯一的存储键:

class StudentsDatabase {
  List<String> Students = [];
  List<int> SelectedRadio = [];
  final String className;
  final Box _box;

  // 构造函数传入班级名称
  StudentsDatabase(this.className) : _box = Hive.box('mybox2');

  // 生成该班级专属的存储键
  String get _studentKey => 'STUDENTS_${className.replaceAll(' ', '_')}';
  String get _radioKey => 'RADIO_${className.replaceAll(' ', '_')}';

  void InitialData() {
    Students = [];
    SelectedRadio = [];
    UpdateDataBase();
  }

  void LoadData() {
    Students = _box.get(_studentKey) ?? [];
    SelectedRadio = _box.get(_radioKey) ?? [];
  }

  void UpdateDataBase() {
    _box.put(_studentKey, Students);
    _box.put(_radioKey, SelectedRadio);
  }
}

步骤2:修改InsideList页面的初始化逻辑

在_InsideListState中,根据传入的班级名称初始化对应的数据库实例:

class _InsideListState extends State<InsideList> {
  final _myBox = Hive.box('mybox2');
  late StudentsDatabase db; // 改成late,后面初始化

  late TextEditingController _textController;

  @override
  void initState() {
    // 根据班级名称创建专属数据库实例
    db = StudentsDatabase(widget.name);
    // 加载该班级的数据
    if (_myBox.get(db._studentKey) == null) {
      db.InitialData();
    } else {
      db.LoadData();
    }
    super.initState();
    _textController = TextEditingController();
  }

  // 其他代码保持不变...
}

补充:删除班级时清理对应学生数据

在班级列表页面的删除按钮逻辑里添加数据清理,避免存储冗余:

TextButton(
  child: Text('Delete'),
  onPressed: () {
    setState(() {
      // 先获取要删除的班级名称
      String className = db.Items[index];
      // 删除班级
      db.Items.removeAt(index);
      db.UpdateDataBase();
      // 删除该班级的学生数据
      final studentBox = Hive.box('mybox2');
      String studentKey = 'STUDENTS_${className.replaceAll(' ', '_')}';
      String radioKey = 'RADIO_${className.replaceAll(' ', '_')}';
      studentBox.delete(studentKey);
      studentBox.delete(radioKey);
      Navigator.pop(context);
    });
  },
),

这样每个班级的学生数据都会存在独立的Hive键下,同一个InsideList类就能根据班级名称加载对应的数据,不会再出现数据混同的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 23:45:41