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

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);
  }
}

关键修复点说明

  1. Firestore数据兼容:将IconData转为字符串存储,UI层再转回对应图标,解决对象无法序列化的问题
  2. 异步操作规范:用async/await包裹Firestore操作,确保数据写入完成后再执行页面跳转
  3. UI数据绑定:将GridView的itemCount绑定到Firestore快照的文档数量,实现实时更新
  4. 拼写错误修正:统一字段名和参数名,避免数据读取失败
  5. 全类别支持:补充所有设备类别的配置逻辑,确保所有有效类别均可添加
  6. 错误处理完善:添加try/catch捕获异常,给出明确错误提示
  7. 删除逻辑优化:用文档ID删除设备,避免因数据排序变化导致的索引错误

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 20:07:03