使用 Provider 时 Flutter Web 和 Firebase List<String> 和 List <dynamic> 错误

Rog*_*rto 2 dart firebase flutter google-cloud-firestore

我使用 Flutter 构建了一个网络应用程序。几个月前构建和部署没有任何问题。今天跳回代码,没有更新任何代码,现在收到以下错误:

\n
    Error:Expected a value of type 'List<String>', but got one of type 'List<dynamic>'\n\xe2\x95\x90\xe2\x95\x90\xe2\x95\xa1 EXCEPTION CAUGHT BY WIDGETS LIBRARY \xe2\x95\x9e\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\nThe following JSNoSuchMethodError was thrown building NewHomeScreen(dirty, dependencies:\n[_EffectiveTickerMode, _InheritedProviderScope<List<ContentModel>>, MediaQuery], state:\n_NewHomeScreenState#295f1(tickers: tracking 2 tickers)):\nNoSuchMethodError: invalid member on null: 'length'\n
Run Code Online (Sandbox Code Playgroud)\n

这是我从 Firebase 获取数据的位置和方式:

\n
void main() => runApp(MyApp());\n\nclass MyApp extends StatefulWidget {\n  // This widget is the root of your application.\n  @override\n  _MyAppState createState() => _MyAppState();\n}\n\n@override\nvoid initState() {}\n\nclass _MyAppState extends State<MyApp> {\n\n  @override\n  Widget build(BuildContext context) {\n    final linksCollection = Firestore.instance.collection('links');\n    final contentCollection = Firestore.instance.collection('content');\n\n    final contentObjects = contentCollection.snapshots().map((snapshot) {\n      return snapshot.documents\n          .map((doc) => ContentModel.fromDocument(doc))\n          .toList();\n    });\n\n    return MultiProvider(\n      providers: [\n        StreamProvider<List<ContentModel>>(\n          create: (_) => contentObjects,\n          initialData: [],\n          catchError: (BuildContext context, e) {\n            print("Error:$e");\n            return null;\n          },\n        ),\n\n        Provider<CollectionReference>(create: (_) => linksCollection),\n\n      ],\n      child: MaterialApp(\n        title: 'My App',\n        debugShowCheckedModeBanner: false,\n        theme: ThemeData(primarySwatch: Colors.blue, fontFamily: 'IBM_Plex'),\n        initialRoute: '/',\n        routes: {'/': (context) => NewHomeScreen()},\n      ),\n    );\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后,我通过使用 Provider 访问该数据,在整个应用程序中使用这些数据,如下所示:

\n
class NewHomeScreen extends StatefulWidget {\n  @override\n  _NewHomeScreenState createState() => _NewHomeScreenState();\n}\n\nclass _NewHomeScreenState extends State<NewHomeScreen>\n    with TickerProviderStateMixin {\n\n  @override\n  void initState() {\n    super.initState();\n\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final contentObjects = Provider.of<List<ContentModel>>(context);\n\n    List<ContentModel> expertList = [];\n\n    for (var data in contentObjects) {\n      if(data.topic == 'expert') {\n        expertList.add(data);\n      }\n    }\n\n    return Scaffold(\n      body: CustomScrollView(\n        slivers: <Widget>[\n          SliverAppBar(\n            leading: Container(\n                child: Padding(\n              padding: EdgeInsets.only(left: 10.0),\n              child: GestureDetector(\n                onTap: () {\n                  Navigator.push(\n                    context,\n                    MaterialPageRoute(\n                      builder: (context) => NewHomeScreen(),\n                    ),\n                  );\n                },\n             \n              ),\n            )\n\n                ),\n            backgroundColor: appBarColor,\n            expandedHeight: 50.0,\n            pinned: true,\n            flexibleSpace: FlexibleSpaceBar(\n              title: Align(\n                alignment: Alignment.center,\n              ),\n              centerTitle: true,\n\n              stretchModes: [\n                StretchMode.blurBackground,\n                StretchMode.zoomBackground\n              ],\n              background: Image.network(\n                'https://www.image.com',\n                fit: BoxFit.cover,\n              ),\n            ),\n            actions: <Widget>[\n              InkResponse(\n                onTap: () {\n                  Navigator.push(\n                    context,\n                    SlideRightRoute(\n                      page: SearchScreen(),\n                    ),\n                  );\n                },\n                child: new Padding(\n                  padding: const EdgeInsets.all(12.0),\n                  child: Icon(\n                    Icons.search,\n                    size: 26.0,\n                    color: Colors.white,\n                  ),\n                ),\n              ),\n            ],\n          ),\n          SliverToBoxAdapter(\n            child: Column(\n              children: <Widget>[\n                FadeIn(1.00, Center(child: HeaderWidget())),\n                FadeIn(2.33, Center(child: HashtagRow())),\n                SizedBox(\n                  height: 20,\n                ),\n                SizedBox(height: 50),\n                FadeIn(\n                  2.66,\n                  SectionContainer(\n                    sectionTitle: "Expertise in focus",\n                    child: Padding(\n                      padding: EdgeInsets.only(top: 13, bottom: 13),\n                      child: Container(\n                        height: 450,\n                        child: ListView.builder(\n                          padding: EdgeInsets.only(left: 50, right: 50),\n                          scrollDirection: Axis.horizontal,\n                          itemCount: expertList.length,\n                          itemBuilder: (ctx, index) {\n                            return GestureDetector(\n                              onTap: () {\n                                Navigator.push(\n                                  context,\n                                  MaterialPageRoute(\n                                    builder: (context) => ExpertDetailsScreen(\n                                      contentModel: expertList[index],\n                                    ),\n                                  ),\n                                );\n                              },\n                              child: Column(\n                                children: <Widget>[\n                                  Padding(\n                                    padding: EdgeInsets.only(\n                                      left: 15.0,\n                                      right: 15.0,\n                                    ),\n                                    child: Hero(\n                                      tag: expertList[index].title.toString(),\n                                      child: Align(\n                                        alignment: Alignment.centerLeft,\n                                        child: CircleAvatar(\n                                          radius: 150.0,\n                                          backgroundImage: NetworkImage(\n                                              expertList[index].imglink),\n                                          backgroundColor: Colors.transparent,\n                                        ),\n                                      ),\n                                    ),\n                                  ),\n                                  Container(\n                                    decoration: BoxDecoration(\n                                      borderRadius: BorderRadius.circular(8.0),\n\n                                    ),\n                                    child: Padding(\n                                      padding: const EdgeInsets.all(10),\n                                      child: Center(\n                                        child: Text(\n                                          expertList[index].tags[1],\n                                          textAlign: TextAlign.center,\n                                          style: forumNameTextStyleTwo,\n                                        ),\n                                      ),\n                                    ),\n                                  ),\n                                  SizedBox(height: 3),\n                                  Text(\n                                    expertList[index].title,\n                                    textAlign: TextAlign.center,\n                                    style: labelTextStyle,\n                                  ),\n                                ],\n                              ),\n                            );\n                          },\n                        ),\n                      ),\n                    ),\n                  ),\n                ),\n                SizedBox(height: 50)\n              ],\n            ),\n          )\n        ],\n      ),\n      floatingActionButton: FloatingActionButton.extended(\n        onPressed: () {\n          Navigator.push(\n            context,\n            ScaleRoute(\n              page: AddResource(),\n            ),\n          );\n        },\n        label: Text('Suggest a resource'),\n        icon: Icon(Icons.add),\n        backgroundColor: myColor,\n      ),\n\n    );\n  }\n\n  void htmlOpenLink(String s) {\n    html.window.open(s, '_blank');\n  }\n}\n\nclass SlideRightRoute extends PageRouteBuilder {\n  final Widget page;\n  SlideRightRoute({this.page})\n      : super(\n          pageBuilder: (\n            BuildContext context,\n            Animation<double> animation,\n            Animation<double> secondaryAnimation,\n          ) =>\n              page,\n          transitionsBuilder: (\n            BuildContext context,\n            Animation<double> animation,\n            Animation<double> secondaryAnimation,\n            Widget child,\n          ) =>\n              SlideTransition(\n            position: Tween<Offset>(\n              begin: const Offset(-1, 0),\n              end: Offset.zero,\n            ).animate(\n              CurvedAnimation(\n                parent: animation,\n                curve: Curves.fastOutSlowIn,\n              ),\n            ),\n            child: child,\n          ),\n        );\n}\n\nclass ScaleRoute extends PageRouteBuilder {\n  final Widget page;\n  ScaleRoute({this.page})\n      : super(\n          pageBuilder: (\n            BuildContext context,\n            Animation<double> animation,\n            Animation<double> secondaryAnimation,\n          ) =>\n              page,\n          transitionsBuilder: (\n            BuildContext context,\n            Animation<double> animation,\n            Animation<double> secondaryAnimation,\n            Widget child,\n          ) =>\n              ScaleTransition(\n            scale: Tween<double>(\n              begin: 0.0,\n              end: 1.0,\n            ).animate(\n              CurvedAnimation(\n                parent: animation,\n                curve: Curves.fastOutSlowIn,\n              ),\n            ),\n            child: child,\n          ),\n        );\n}\n\nclass MyCustomClipper extends CustomClipper<Path> {\n  final double distanceFromWall = 12;\n  final double controlPointDistanceFromWall = 2;\n\n  @override\n  Path getClip(Size size) {\n    final double height = size.height;\n    final double halfHeight = size.height * 0.5;\n    final double width = size.width;\n\n    Path clippedPath = Path();\n    clippedPath.moveTo(0, halfHeight);\n    clippedPath.lineTo(0, height - distanceFromWall);\n    clippedPath.quadraticBezierTo(0 + controlPointDistanceFromWall,\n        height - controlPointDistanceFromWall, 0 + distanceFromWall, height);\n    clippedPath.lineTo(width, height);\n    clippedPath.lineTo(width, 0 + 30.0);\n    clippedPath.quadraticBezierTo(width - 5, 0 + 5.0, width - 35, 0 + 15.0);\n    clippedPath.close();\n    return clippedPath;\n  }\n\n  @override\n  bool shouldReclip(CustomClipper<Path> oldClipper) {\n    return true;\n  }\n}\n\nclass CustomShapeBorder extends ShapeBorder {\n  final double distanceFromWall = 12;\n  final double controlPointDistanceFromWall = 2;\n\n  @override\n  EdgeInsetsGeometry get dimensions => null;\n\n  @override\n  Path getInnerPath(Rect rect, {TextDirection textDirection}) {\n    return null;\n  }\n\n  @override\n  Path getOuterPath(Rect rect, {TextDirection textDirection}) {\n    return getClip(Size(220.0, 70.0));\n  }\n\n  @override\n  void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}\n\n  @override\n  ShapeBorder scale(double t) {\n    return null;\n  }\n\n  Path getClip(Size size) {\n    Path clippedPath = Path();\n    clippedPath.moveTo(0 + distanceFromWall, 0);\n    clippedPath.quadraticBezierTo(0 + controlPointDistanceFromWall,\n        0 + controlPointDistanceFromWall, 0, 0 + distanceFromWall);\n    clippedPath.lineTo(0, size.height - distanceFromWall);\n    clippedPath.quadraticBezierTo(\n        0 + controlPointDistanceFromWall,\n        size.height - controlPointDistanceFromWall,\n        0 + distanceFromWall,\n        size.height);\n    clippedPath.lineTo(size.width - distanceFromWall, size.height);\n    clippedPath.quadraticBezierTo(\n        size.width - controlPointDistanceFromWall,\n        size.height - controlPointDistanceFromWall,\n        size.width,\n        size.height - distanceFromWall);\n    clippedPath.lineTo(size.width, size.height * 0.6);\n    clippedPath.quadraticBezierTo(\n        size.width - 1,\n        size.height * 0.6 - distanceFromWall,\n        size.width - distanceFromWall,\n        size.height * 0.6 - distanceFromWall - 3);\n    clippedPath.lineTo(0 + distanceFromWall + 6, 0);\n    clippedPath.close();\n    return clippedPath;\n  }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这是数据的模型类:

\n
class ContentModel {\n  String title;\n  String description;\n  String imglink;\n  int contentId;\n  List<String> tags;\n  List<String> focusAreas;\n  int likeCount;\n  String myIcon;\n  bool isNew;\n  String content;\n  String contentLink;\n  String appColor;\n  double positionVar;\n  String detailScreenLink;\n  String documentId;\n  String topic;\n  String hashtag;\n\n  ContentModel(\n      {this.title,\n      this.description,\n      this.imglink,\n      this.contentId,\n      this.tags,\n      this.likeCount,\n      this.myIcon,\n      this.isNew,\n      this.content,\n      this.contentLink,\n      this.appColor,\n      this.positionVar,\n      this.detailScreenLink,\n      this.documentId,\n      this.topic,\n      this.focusAreas,\n      this.hashtag});\n\n  Map<String, dynamic> toMap() {\n    return {\n      'title': title,\n      'description': description,\n      'imglink': imglink,\n      'contentId': contentId,\n      'tags': tags,\n      'likeCount': likeCount,\n      'isNew': isNew,\n      'content': content,\n      'contentLink': contentLink,\n      'appColor': appColor,\n      'positionVar': positionVar,\n      'detailScreenLink': detailScreenLink,\n      'documentId': documentId,\n      'topic': topic,\n      'focusAreas': focusAreas,\n      'hashtag': hashtag\n    };\n  }\n\n  static ContentModel fromDocument(DocumentSnapshot document) {\n    if (document == null || document.data == null) return null;\n\n    return ContentModel(\n        documentId: document.documentID,\n        imglink: document.data['imglink'],\n        title: document.data['title'],\n        description: document.data['description'],\n        likeCount: document.data['likeCount'],\n        tags: document.data['tags'],\n        isNew: document.data['isNew'],\n        content: document.data['content'],\n        contentLink: document.data['contentLink'],\n        appColor: document.data['appColor'],\n        positionVar: document.data['positionVar'],\n        detailScreenLink: document.data['detailScreenLink'],\n        topic: document.data['topic'],\n        focusAreas: document.data['focusAreas'],\n        hashtag: document.data['hashtag']);\n  }\n\n  Map toJson() => {\n        'title': title,\n        'description': description,\n        'imglink': imglink,\n        'contentId': contentId,\n        'tags': tags,\n        'likeCount': likeCount,\n        'isNew': isNew,\n        'content': content,\n        'contentLink': contentLink,\n        'appColor': appColor,\n        'positionVar': positionVar,\n        'detailScreenLink': detailScreenLink,\n        'documentId': documentId,\n        'topic': topic,\n        'focusAreas': focusAreas,\n        'hashtag': hashtag\n      };\n}\n
Run Code Online (Sandbox Code Playgroud)\n

You*_*lid 6

给定

List<dynamic> dynamicList;
Run Code Online (Sandbox Code Playgroud)

您可以使用

var stringList = List<String>.from(dlist);
Run Code Online (Sandbox Code Playgroud)

将 a 转换List<dynamic>List<String>

因此你需要修复你的模式:

  static ContentModel fromDocument(DocumentSnapshot document) {
if (document == null || document.data == null) return null;

return ContentModel(
    documentId: document.documentID,
    imglink: document.data['imglink'],
    title: document.data['title'],
    description: document.data['description'],
    likeCount: document.data['likeCount'],
    tags:  List<String>.from(document.data['tags']),// to convert a List<dynamic> to List<String>
    isNew: document.data['isNew'],
    content: document.data['content'],
    contentLink: document.data['contentLink'],
    appColor: document.data['appColor'],
    positionVar: document.data['positionVar'],
    detailScreenLink: document.data['detailScreenLink'],
    topic: document.data['topic'],
    focusAreas:  List<String>.from(document.data['focusAreas']), //to convert a List<dynamic> to List<String>
    hashtag: document.data['hashtag']);}
Run Code Online (Sandbox Code Playgroud)