TransWikia.com

Flutter Web and Firebase List and List error using Provider

Stack Overflow Asked by Rogerto on November 27, 2021

I’ve built a web app using Flutter. Built and deployed with no issues a couple of months ago. Have jumped back into the code today without updating any code and now am getting the following error:

    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'

This is where and how I get data from 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()},
      ),
    );
  }
}

I then consume this data throughout the app by accessing it using Provider like so:

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

}

Here is the model class for the data:

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 Answers

I think the issue arises from the builder parameter, itemCount: expertList.length.

One possible case could be, that the expertList is not yet populated from the backend, when the widget build is being triggered. I'd suggest using a wait parameter to ensure the data has been populated before rendering the builder on screen. In my experience, I was able to achieve this functionality using a ModalProgressHud configured to my state.waiting boolean.

Another solution is to just add the null checks. Quick fix could be:

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

This ensures that the ListView Builder is only added to the widget tree if already populated. Hence, bypasses the null issues.

Answered by Hamza Ahmad on November 27, 2021

Given

List<dynamic> dynamicList;

You can use

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

to convert a List<dynamic> to List<String>

Therefore you need to fix your mode:

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

Answered by Yousif khalid on November 27, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP