2

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
      };
}
1
  • Probably getting an NPE here: expertList[index].tags[1] - try adding null safety and check if you still have the same issue. Commented Jul 22, 2020 at 8:28

2 Answers 2

6

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']);}
Sign up to request clarification or add additional context in comments.

Comments

1
+150

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.