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

Firestore StreamBuilder类型转换错误:List无法转为List<Map<String,dynamic>>

问题:Firestore实时同步棋盘展示时触发类型转换错误

错误信息

The following _TypeError was thrown building StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>(dirty, state: _StreamBuilderBaseState<DocumentSnapshot<Map<String, dynamic>>, AsyncSnapshot<DocumentSnapshot<Map<String, dynamic>>> #954da):
type 'List' is not a subtype of type 'List<Map<String, dynamic>>' in type cast

场景与代码

我要实现和Firestore实时同步的棋盘展示功能,创建房间文档后用StreamBuilder拉取数据展示,却触发了上面的错误。相关代码如下:

Firestore监听方法

Stream<DocumentSnapshot<Map<String, dynamic>>> watchRoom(String roomId) {  
  return FirebaseFirestore.instance.collection("rooms").doc(roomId).snapshots();  
}

棋盘组件代码

class OnlineBoardWidget extends StatelessWidget {  
  const OnlineBoardWidget({super.key});  
  
  @override  
  Widget build(BuildContext context) {  
    final columnCount = OnlineGameController.to.room.board.columnCount;  
    final rowCount = OnlineGameController.to.room.board.rowCount;  
    logger.t('build online board ($rowCount, $columnCount)');  
    
    return StreamBuilder(  
    // 监听Firestore上的房间文档
      stream: firestoreService.watchRoom("ec483c14-f414-44ae-bea1-b84e409331f0"),  
        builder: (context, snapshot) {  
          if (snapshot.connectionState == ConnectionState.waiting) {  
            return CircularProgressIndicator();  
          } else if (snapshot.hasError) {  
            return Text('Error: ${snapshot.error}');  
          } else {  
            Map<String, dynamic> json = snapshot.data!.data()!;  
            logger.i(json);  // 能正常打印json数据
            OnlineRoom room = OnlineRoom.fromJson(json);  // 此处触发错误
            logger.i(room);  
            return Text(room.toString(), style: kNormalText,);  
          }  
        },  
    );  
  }  
}

测试情况

我怀疑是OnlineRoom.fromJson()的问题,于是在main函数里写了测试代码,直接用本地JSON片段调用fromJson()完全正常,没有报错:

测试代码

final object = OnlineRoom.fromJson(roomJsonSnippet);  
logger.i(object); // 无错误,正常打印对象

测试用JSON片段

{  
    "winCount": 5,  
     "historyBoard": {  
         "cells": {  
             "0": [  
                 {  
                     "column": 0,  
                     "row": 0,  
                     "state": "normal",  
                     "content": "noSeed"  
                   },  
                 {  
                     "column": 1,  
                     "row": 0,  
                     "state": "normal",  
                     "content": "noSeed"  
                   }  
               ],  
             "1": [  
                 {  
                     "column": 0,  
                     "state": "normal",  
                     "row": 1,  
                     "content": "noSeed"  
                   },  
                 {  
                     "column": 1,  
                     "row": 1,  
                     "state": "normal",  
                     "content": "noSeed"  
                   }  
               ]  
           },  
         "rowCount": 2,  
         "columnCount": 2  
       },  
     "name": "Untitled Room",  
     "historyRoundIndex": 0,  
     "id": "ec483c14-f414-44ae-bea1-b84e409331f0",  
     "state": "playing",  
     "rounds": [  
         {  
             "historyTurns": [],  
             "currentPlayerIndex": 0,  
             "_historyPlayerIndex": 0,  
             "players": [  
                 {  
                     "score": 0,  
                     "finalScore": null,  
                     "seed": "cross",  
                     "name": "Player 1",  
                     "initialScore": 0,  
                     "index": 0  
                   },  
                 {  
                     "score": 0,  
                     "finalScore": null,  
                     "seed": "nought",  
                     "initialScore": 0,  
                     "name": "Player 2",  
                     "index": 1  
                   }  
               ],  
             "historyTurnIndex": 0,  
             "winTurnIndex": null,  
             "index": 0,  
             "currentTurnIndex": 0,  
             "winnerIndex": null,  
             "turns": []  
           }  
       ],  
     "board": {  
         "cells": {  
             "0": [  
                 {  
                     "column": 0,  
                     "state": "normal",  
                     "row": 0,  
                     "content": "noSeed"  
                   },  
                 {  
                     "column": 1,  
                     "row": 0,  
                     "state": "normal",  
                     "content": "noSeed"  
                   }  
               ],  
             "1": [  
                 {  
                     "column": 0,  
                     "state": "normal",  
                     "row": 1,  
                     "content": "noSeed"  
                   },  
                 {  
                     "column": 1,  
                     "row": 1,  
                     "state": "normal",  
                     "content": "noSeed"  
                   }  
               ]  
           },  
         "rowCount": 2,  
         "columnCount": 2  
       },  
     "currentRoundIndex": 0  
   }

疑问

为什么本地测试fromJson()正常,但从Firestore拉取数据调用就会出现类型转换错误?


解决方案

核心原因

Firestore返回的数组是List<dynamic>类型,而非Dart中严格的List<Map<String, dynamic>>。你的fromJson方法如果直接将其强制转换为List<Map<String, dynamic>>,就会触发类型错误。而本地JSON是直接用Dart构造的Map<String, dynamic>,其中的List已经是List<Map<String, dynamic>>类型,所以不会报错。

修复步骤

  1. 修改fromJson中的List转换逻辑:不要直接强制类型转换,显式遍历List并转换每个元素的类型。
    比如原本的错误写法:
    List<Map<String, dynamic>> cellsList = json['cells'] as List<Map<String, dynamic>>;
    
    改成:
    List<Map<String, dynamic>> cellsList = (json['cells'] as List<dynamic>)
        .map((item) => item as Map<String, dynamic>)
        .toList();
    
  2. 检查所有嵌套数组:重点处理board.cells、historyBoard.cells、rounds、players这些嵌套结构里的数组,确保所有List都经过显式类型转换。
  3. 可选:使用代码生成工具:比如json_serializable包,它生成的fromJson方法会自动兼容Firestore返回的动态类型List,避免手动转换的错误。

验证方法

修改后重新运行StreamBuilder,确认错误消失。也可以在fromJson中打印Firestore返回List的runtimeType,对比本地测试的List类型,确认差异已解决。


内容的提问来源于stack exchange,提问作者Thắng Nguyễn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 03:07:02