Flutter中FirebaseFirestore添加设备报错:输入法开启光标监控
问题解决:Firestore设备添加失败及主页展示异常
核心问题分析
「The input method toggled cursor monitoring on」是输入法触发的警告,并非功能失效的根本原因,真正的问题集中在Firestore数据存储逻辑和UI绑定上:
- Firestore不支持直接存储
IconData对象,会导致数据写入失败 - 主页GridView的
itemCount未绑定Firestore快照数据,新添加设备无法实时显示 - Firestore操作未做异步处理,可能导致弹窗提前关闭或操作未完成
- 存在字段拼写错误(
cassPath应为classPath) - 仅处理了Light类别的设备,其他类别点击添加无任何逻辑
修正后的代码
1. 修复添加设备函数(addDevice)
void addDevice() async { showDialog( context: context, barrierDismissible: false, builder: (context) => const Center( child: CircularProgressIndicator( backgroundColor: Colors.black, color: Colors.purple, ), ), ); // 输入验证逻辑 if (_deviceNameController.text.isEmpty || _deviceClassController.text.isEmpty) { Navigator.pop(context); showErrorMessage("Please fill out all fields".tr); return; } if (_deviceNameController.text.length > 20) { Navigator.pop(context); showErrorMessage("Device Name can be a maximum of 20 characters long".tr); return; } if (_deviceNameController.text.length <= 1) { Navigator.pop(context); showErrorMessage("Device Name must be at least 2 characters long".tr); return; } final validClasses = [ "Light".tr, "Power Socket".tr, "Screen".tr, "Camera".tr, "Gate".tr ]; if (!validClasses.contains(_deviceClassController.text)) { Navigator.pop(context); showErrorMessage("Class not found".tr); return; } // 预定义各设备类别的配置(用字符串存储图标标识,适配Firestore数据类型) Map<String, Map<String, dynamic>> deviceConfig = { "Light".tr: { "classPath": "assets/icons/lamp.png", "textOn": "On", "textOff": "Off", "iconOn": "CupertinoIcons.sun_max", "iconOff": "CupertinoIcons.moon" }, "Power Socket".tr: { "classPath": "assets/icons/socket.png", "textOn": "On", "textOff": "Off", "iconOn": "CupertinoIcons.power", "iconOff": "CupertinoIcons.power_off" }, "Screen".tr: { "classPath": "assets/icons/screen.png", "textOn": "On", "textOff": "Off", "iconOn": "CupertinoIcons.tv", "iconOff": "CupertinoIcons.tv_slash" }, "Camera".tr: { "classPath": "assets/icons/camera.png", "textOn": "Active", "textOff": "Inactive", "iconOn": "CupertinoIcons.camera", "iconOff": "CupertinoIcons.camera_slash" }, "Gate".tr: { "classPath": "assets/icons/gate.png", "textOn": "Open", "textOff": "Close", "iconOn": "CupertinoIcons.lock_open", "iconOff": "CupertinoIcons.lock" } }; try { // 写入Firestore await FirebaseFirestore.instance.collection('UserDevices').add({ 'deviceName': _deviceNameController.text, 'classPath': deviceConfig[_deviceClassController.text]!['classPath'], 'textOn': deviceConfig[_deviceClassController.text]!['textOn'], 'textOff': deviceConfig[_deviceClassController.text]!['textOff'], 'iconOn': deviceConfig[_deviceClassController.text]!['iconOn'], 'iconOff': deviceConfig[_deviceClassController.text]!['iconOff'], }); // 清空输入框 _deviceNameController.clear(); _deviceClassController.clear(); Navigator.pop(context); // 关闭加载弹窗 Navigator.pop(context); // 返回主页 } catch (e) { Navigator.pop(context); showErrorMessage("Failed to add device: ${e.toString()}".tr); } }
2. 修复主页GridView的StreamBuilder
Expanded( child: RefreshIndicator( onRefresh: () async { await Future.delayed(const Duration(milliseconds: 500)); setState(() {}); }, backgroundColor: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.surface, displacement: 2, child: StreamBuilder<QuerySnapshot>( stream: FirebaseFirestore.instance.collection('UserDevices').snapshots(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator.adaptive()); } if (snapshot.hasError) { return Center(child: Text("Error: ${snapshot.error}")); } if (!snapshot.hasData || snapshot.data!.docs.isEmpty) { return const Center(child: Text("No devices added yet")); } return GridView.builder( itemCount: snapshot.data!.docs.length, // 绑定Firestore快照文档数量 gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, ), itemBuilder: (context, index) { final deviceDoc = snapshot.data!.docs[index]; final deviceData = deviceDoc.data() as Map<String, dynamic>; // 将存储的字符串转为IconData IconData getIconData(String iconStr) { switch (iconStr) { case "CupertinoIcons.sun_max": return CupertinoIcons.sun_max; case "CupertinoIcons.moon": return CupertinoIcons.moon; case "CupertinoIcons.power": return CupertinoIcons.power; case "CupertinoIcons.power_off": return CupertinoIcons.power_off; case "CupertinoIcons.tv": return CupertinoIcons.tv; case "CupertinoIcons.tv_slash": return CupertinoIcons.tv_slash; case "CupertinoIcons.camera": return CupertinoIcons.camera; case "CupertinoIcons.camera_slash": return CupertinoIcons.camera_slash; case "CupertinoIcons.lock_open": return CupertinoIcons.lock_open; case "CupertinoIcons.lock": return CupertinoIcons.lock; default: return CupertinoIcons.device_phone_portrait; } } return DeviceTile( deviceName: deviceData['deviceName'], // 修正参数名拼写错误 classPath: deviceData['classPath'], // 修正字段名拼写错误 textOn: deviceData['textOn'], textOff: deviceData['textOff'], iconOn: getIconData(deviceData['iconOn']), iconOff: getIconData(deviceData['iconOff']), onLongPress: () => deleteDevice(deviceDoc.id), // 用文档ID删除,避免索引错位 ); }, ); }, ), ), );
3. 优化删除设备函数(用文档ID)
void deleteDevice(String docId) async { try { await FirebaseFirestore.instance.collection('UserDevices').doc(docId).delete(); } catch (e) { showErrorMessage("Failed to delete device: ${e.toString()}".tr); } }
关键修复点说明
- Firestore数据兼容:将
IconData转为字符串存储,UI层再转回对应图标,解决对象无法序列化的问题 - 异步操作规范:用
async/await包裹Firestore操作,确保数据写入完成后再执行页面跳转 - UI数据绑定:将GridView的
itemCount绑定到Firestore快照的文档数量,实现实时更新 - 拼写错误修正:统一字段名和参数名,避免数据读取失败
- 全类别支持:补充所有设备类别的配置逻辑,确保所有有效类别均可添加
- 错误处理完善:添加
try/catch捕获异常,给出明确错误提示 - 删除逻辑优化:用文档ID删除设备,避免因数据排序变化导致的索引错误
内容的提问来源于stack exchange,提问作者burgerexe18
相关产品推荐
相关产品推荐

