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
相关产品推荐
相关产品推荐

