Ошибка Flutter Web и Firebase List <String>и List <dynamic>с использованием поставщика - PullRequest
1 голос
/ 14 июля 2020

Я создал веб-приложение с помощью Flutter. Создан и развернут без проблем за пару месяцев go. Вернулись к коду сегодня, не обновляя код, и теперь получаю следующую ошибку:

    Error:Expected a value of type 'List<String>', but got one of type 'List<dynamic>'
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following JSNoSuchMethodError was thrown building NewHomeScreen(dirty, dependencies:
[_EffectiveTickerMode, _InheritedProviderScope<List<ContentModel>>, MediaQuery], state:
_NewHomeScreenState#295f1(tickers: tracking 2 tickers)):
NoSuchMethodError: invalid member on null: 'length'

Вот где и как я получаю данные из Firebase:

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  // This widget is the root of your application.
  @override
  _MyAppState createState() => _MyAppState();
}

@override
void initState() {}

class _MyAppState extends State<MyApp> {

  @override
  Widget build(BuildContext context) {
    final linksCollection = Firestore.instance.collection('links');
    final contentCollection = Firestore.instance.collection('content');

    final contentObjects = contentCollection.snapshots().map((snapshot) {
      return snapshot.documents
          .map((doc) => ContentModel.fromDocument(doc))
          .toList();
    });

    return MultiProvider(
      providers: [
        StreamProvider<List<ContentModel>>(
          create: (_) => contentObjects,
          initialData: [],
          catchError: (BuildContext context, e) {
            print("Error:$e");
            return null;
          },
        ),

        Provider<CollectionReference>(create: (_) => linksCollection),

      ],
      child: MaterialApp(
        title: 'My App',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(primarySwatch: Colors.blue, fontFamily: 'IBM_Plex'),
        initialRoute: '/',
        routes: {'/': (context) => NewHomeScreen()},
      ),
    );
  }
}

Я тогда использовать эти данные во всем приложении, обращаясь к ним с помощью Provider следующим образом:

class NewHomeScreen extends StatefulWidget {
  @override
  _NewHomeScreenState createState() => _NewHomeScreenState();
}

class _NewHomeScreenState extends State<NewHomeScreen>
    with TickerProviderStateMixin {

  @override
  void initState() {
    super.initState();

  }

  @override
  Widget build(BuildContext context) {
    final contentObjects = Provider.of<List<ContentModel>>(context);

    List<ContentModel> expertList = [];

    for (var data in contentObjects) {
      if(data.topic == 'expert') {
        expertList.add(data);
      }
    }

    return Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
          SliverAppBar(
            leading: Container(
                child: Padding(
              padding: EdgeInsets.only(left: 10.0),
              child: GestureDetector(
                onTap: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => NewHomeScreen(),
                    ),
                  );
                },
             
              ),
            )

                ),
            backgroundColor: appBarColor,
            expandedHeight: 50.0,
            pinned: true,
            flexibleSpace: FlexibleSpaceBar(
              title: Align(
                alignment: Alignment.center,
              ),
              centerTitle: true,

              stretchModes: [
                StretchMode.blurBackground,
                StretchMode.zoomBackground
              ],
              background: Image.network(
                'https://www.image.com',
                fit: BoxFit.cover,
              ),
            ),
            actions: <Widget>[
              InkResponse(
                onTap: () {
                  Navigator.push(
                    context,
                    SlideRightRoute(
                      page: SearchScreen(),
                    ),
                  );
                },
                child: new Padding(
                  padding: const EdgeInsets.all(12.0),
                  child: Icon(
                    Icons.search,
                    size: 26.0,
                    color: Colors.white,
                  ),
                ),
              ),
            ],
          ),
          SliverToBoxAdapter(
            child: Column(
              children: <Widget>[
                FadeIn(1.00, Center(child: HeaderWidget())),
                FadeIn(2.33, Center(child: HashtagRow())),
                SizedBox(
                  height: 20,
                ),
                SizedBox(height: 50),
                FadeIn(
                  2.66,
                  SectionContainer(
                    sectionTitle: "Expertise in focus",
                    child: Padding(
                      padding: EdgeInsets.only(top: 13, bottom: 13),
                      child: Container(
                        height: 450,
                        child: ListView.builder(
                          padding: EdgeInsets.only(left: 50, right: 50),
                          scrollDirection: Axis.horizontal,
                          itemCount: expertList.length,
                          itemBuilder: (ctx, index) {
                            return GestureDetector(
                              onTap: () {
                                Navigator.push(
                                  context,
                                  MaterialPageRoute(
                                    builder: (context) => ExpertDetailsScreen(
                                      contentModel: expertList[index],
                                    ),
                                  ),
                                );
                              },
                              child: Column(
                                children: <Widget>[
                                  Padding(
                                    padding: EdgeInsets.only(
                                      left: 15.0,
                                      right: 15.0,
                                    ),
                                    child: Hero(
                                      tag: expertList[index].title.toString(),
                                      child: Align(
                                        alignment: Alignment.centerLeft,
                                        child: CircleAvatar(
                                          radius: 150.0,
                                          backgroundImage: NetworkImage(
                                              expertList[index].imglink),
                                          backgroundColor: Colors.transparent,
                                        ),
                                      ),
                                    ),
                                  ),
                                  Container(
                                    decoration: BoxDecoration(
                                      borderRadius: BorderRadius.circular(8.0),

                                    ),
                                    child: Padding(
                                      padding: const EdgeInsets.all(10),
                                      child: Center(
                                        child: Text(
                                          expertList[index].tags[1],
                                          textAlign: TextAlign.center,
                                          style: forumNameTextStyleTwo,
                                        ),
                                      ),
                                    ),
                                  ),
                                  SizedBox(height: 3),
                                  Text(
                                    expertList[index].title,
                                    textAlign: TextAlign.center,
                                    style: labelTextStyle,
                                  ),
                                ],
                              ),
                            );
                          },
                        ),
                      ),
                    ),
                  ),
                ),
                SizedBox(height: 50)
              ],
            ),
          )
        ],
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () {
          Navigator.push(
            context,
            ScaleRoute(
              page: AddResource(),
            ),
          );
        },
        label: Text('Suggest a resource'),
        icon: Icon(Icons.add),
        backgroundColor: myColor,
      ),

    );
  }

  void htmlOpenLink(String s) {
    html.window.open(s, '_blank');
  }
}

class SlideRightRoute extends PageRouteBuilder {
  final Widget page;
  SlideRightRoute({this.page})
      : super(
          pageBuilder: (
            BuildContext context,
            Animation<double> animation,
            Animation<double> secondaryAnimation,
          ) =>
              page,
          transitionsBuilder: (
            BuildContext context,
            Animation<double> animation,
            Animation<double> secondaryAnimation,
            Widget child,
          ) =>
              SlideTransition(
            position: Tween<Offset>(
              begin: const Offset(-1, 0),
              end: Offset.zero,
            ).animate(
              CurvedAnimation(
                parent: animation,
                curve: Curves.fastOutSlowIn,
              ),
            ),
            child: child,
          ),
        );
}

class ScaleRoute extends PageRouteBuilder {
  final Widget page;
  ScaleRoute({this.page})
      : super(
          pageBuilder: (
            BuildContext context,
            Animation<double> animation,
            Animation<double> secondaryAnimation,
          ) =>
              page,
          transitionsBuilder: (
            BuildContext context,
            Animation<double> animation,
            Animation<double> secondaryAnimation,
            Widget child,
          ) =>
              ScaleTransition(
            scale: Tween<double>(
              begin: 0.0,
              end: 1.0,
            ).animate(
              CurvedAnimation(
                parent: animation,
                curve: Curves.fastOutSlowIn,
              ),
            ),
            child: child,
          ),
        );
}

class MyCustomClipper extends CustomClipper<Path> {
  final double distanceFromWall = 12;
  final double controlPointDistanceFromWall = 2;

  @override
  Path getClip(Size size) {
    final double height = size.height;
    final double halfHeight = size.height * 0.5;
    final double width = size.width;

    Path clippedPath = Path();
    clippedPath.moveTo(0, halfHeight);
    clippedPath.lineTo(0, height - distanceFromWall);
    clippedPath.quadraticBezierTo(0 + controlPointDistanceFromWall,
        height - controlPointDistanceFromWall, 0 + distanceFromWall, height);
    clippedPath.lineTo(width, height);
    clippedPath.lineTo(width, 0 + 30.0);
    clippedPath.quadraticBezierTo(width - 5, 0 + 5.0, width - 35, 0 + 15.0);
    clippedPath.close();
    return clippedPath;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    return true;
  }
}

class CustomShapeBorder extends ShapeBorder {
  final double distanceFromWall = 12;
  final double controlPointDistanceFromWall = 2;

  @override
  EdgeInsetsGeometry get dimensions => null;

  @override
  Path getInnerPath(Rect rect, {TextDirection textDirection}) {
    return null;
  }

  @override
  Path getOuterPath(Rect rect, {TextDirection textDirection}) {
    return getClip(Size(220.0, 70.0));
  }

  @override
  void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}

  @override
  ShapeBorder scale(double t) {
    return null;
  }

  Path getClip(Size size) {
    Path clippedPath = Path();
    clippedPath.moveTo(0 + distanceFromWall, 0);
    clippedPath.quadraticBezierTo(0 + controlPointDistanceFromWall,
        0 + controlPointDistanceFromWall, 0, 0 + distanceFromWall);
    clippedPath.lineTo(0, size.height - distanceFromWall);
    clippedPath.quadraticBezierTo(
        0 + controlPointDistanceFromWall,
        size.height - controlPointDistanceFromWall,
        0 + distanceFromWall,
        size.height);
    clippedPath.lineTo(size.width - distanceFromWall, size.height);
    clippedPath.quadraticBezierTo(
        size.width - controlPointDistanceFromWall,
        size.height - controlPointDistanceFromWall,
        size.width,
        size.height - distanceFromWall);
    clippedPath.lineTo(size.width, size.height * 0.6);
    clippedPath.quadraticBezierTo(
        size.width - 1,
        size.height * 0.6 - distanceFromWall,
        size.width - distanceFromWall,
        size.height * 0.6 - distanceFromWall - 3);
    clippedPath.lineTo(0 + distanceFromWall + 6, 0);
    clippedPath.close();
    return clippedPath;
  }

}

Вот класс модели для данных:

class ContentModel {
  String title;
  String description;
  String imglink;
  int contentId;
  List<String> tags;
  List<String> focusAreas;
  int likeCount;
  String myIcon;
  bool isNew;
  String content;
  String contentLink;
  String appColor;
  double positionVar;
  String detailScreenLink;
  String documentId;
  String topic;
  String hashtag;

  ContentModel(
      {this.title,
      this.description,
      this.imglink,
      this.contentId,
      this.tags,
      this.likeCount,
      this.myIcon,
      this.isNew,
      this.content,
      this.contentLink,
      this.appColor,
      this.positionVar,
      this.detailScreenLink,
      this.documentId,
      this.topic,
      this.focusAreas,
      this.hashtag});

  Map<String, dynamic> toMap() {
    return {
      'title': title,
      'description': description,
      'imglink': imglink,
      'contentId': contentId,
      'tags': tags,
      'likeCount': likeCount,
      'isNew': isNew,
      'content': content,
      'contentLink': contentLink,
      'appColor': appColor,
      'positionVar': positionVar,
      'detailScreenLink': detailScreenLink,
      'documentId': documentId,
      'topic': topic,
      'focusAreas': focusAreas,
      'hashtag': hashtag
    };
  }

  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: document.data['tags'],
        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: document.data['focusAreas'],
        hashtag: document.data['hashtag']);
  }

  Map toJson() => {
        'title': title,
        'description': description,
        'imglink': imglink,
        'contentId': contentId,
        'tags': tags,
        'likeCount': likeCount,
        'isNew': isNew,
        'content': content,
        'contentLink': contentLink,
        'appColor': appColor,
        'positionVar': positionVar,
        'detailScreenLink': detailScreenLink,
        'documentId': documentId,
        'topic': topic,
        'focusAreas': focusAreas,
        'hashtag': hashtag
      };
}

Ответы [ 2 ]

1 голос
/ 23 июля 2020

Я думаю, что проблема возникает из-за параметра построителя itemCount: expertList.length.

Один из возможных случаев может заключаться в том, что expertList еще не заполняется из бэкэнда, когда запускается сборка виджета. Я бы предложил использовать параметр ожидания, чтобы убедиться, что данные были заполнены, прежде чем отображать построитель на экране. По моему опыту, мне удалось добиться этой функциональности с помощью ModalProgressHud, настроенного для моего состояния. Wait boolean.

Другое решение - просто добавить нулевые проверки. Быстрое исправление может быть следующим:

expertList.isNotEmpty ? 
ListView.builder(
      padding: EdgeInsets.only(left: 50, right: 50),
      scrollDirection: Axis.horizontal,
      itemCount: expertList.length, ... ) 
    :  Container();

Это гарантирует, что ListView Builder будет добавлен в дерево виджетов только в том случае, если он уже заполнен. Следовательно, обходит нулевые проблемы.

0 голосов
/ 16 июля 2020

Учитывая

List<dynamic> dynamicList;

Вы можете использовать

var stringList = List<String>.from(dlist);

для преобразования 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']);}
...