0

I'm trying to retrieve a stream List of NewsModel from cloud firestore but ultimately failing. Codes and images are as follows.

I have a collection 'news' > document 'USA' > field list. // list is an array which contains list of map. I want to retrieve this list.

How can I retrieve data from firebase, store in the model object and use the model for UI?

Error:

The following NoSuchMethodError was thrown building:
The method '[]' was called on null.
Receiver: null
Tried calling: [](0)

Code are as follow:

Model:

class NewsModel {
  String title, titleImage, brief;
  List aList;

  NewsModel({this.title, this.titleImage, this.brief, this.aList});

  factory NewsModel.fromMap(dynamic fieldData) {
    return NewsModel(
        title: fieldData['title'],
        titleImage: fieldData['titleImage'],
        brief: fieldData['brief'],
        aList: fieldData['mediaDescList']);
  }
}

Controller:

class Controller extends GetxController {
  // onInit
  @override
  void onInit() {
    finalNewsModel.bindStream(streamDemo());
    fetchDocs();
    super.onInit();
  }

  //
  Rxn<List<NewsModel>> finalNewsModel = Rxn<List<NewsModel>>();

  //
  List<NewsModel> get newsModelList => finalNewsModel.value;

  //
  Stream<List<NewsModel>> streamDemo() {
    return FirebaseFirestore.instance
        .collection('news')
        .doc('USA')
        .snapshots()
        .map((ds) {
      var mapData = ds.data();
      List mapList = mapData['list'];
      List<NewsModel> newsModelList = [];
      mapList.forEach((element) {
        newsModelList.add(NewsModel.fromMap(element));
      });
      return newsModelList;
    });
  }
}

UI:

class HomeBody extends StatefulWidget {
  @override
  _HomeBodyState createState() => _HomeBodyState();
}

class _HomeBodyState extends State<HomeBody> {

//
  final _controller = Get.put(Controller());

  //
  NewsModel _newsModel = NewsModel();

     @override
  Widget build(BuildContext context) {
    return Container(
      child: GetBuilder<Controller>(builder: (_controller) {
        if (_controller.newsModelList == null) {
          return Text('Loading');
        } else if (_controller.newsModelList.isEmpty) {
          return Text('Empty List');
        } else {
          return ListView.builder(
            itemCount: _controller.newsModelList.length,
            itemBuilder: (context, index) {
              return MyContainer(
                title: _newsModel.title[index],
                titleImage: _newsModel.titleImage[index],
              );
            },
          );
        }
      }),
    );
  }
}

enter image description here

Raj A
  • 544
  • 9
  • 21

2 Answers2

1

Error 1:

The getter 'length' was called on null.

The error is coming from your null check below:

    if (_controller.newsModelList.length == null) {
      return Text('Loading');
    }

You're checking if the length of the list is null but the list itself is null so you're calling .length on null. That's the reason the error says:

The getter 'length' was called on null.

Solution:

You should rather check if the list itself is null like below:

    if (_controller.newsModelList == null) {
      return Text('Loading');
    }

Now if you want to check if the list is empty, you have to first ensure the list is not null, then you can call .isEmpty() on the list.

You can then add your existing else statement to show the list of MyContainer widgets.

Error 2:

The following NoSuchMethodError was thrown building: The method '[]' was called on null. Receiver: null Tried calling:

This error is because you're trying to get a value of a null object. And it occurs in the lines below:

    title: _newsModel.title[index],
    titleImage: _newsModel.titleImage[index],

This is because _newsModel.title and _newsModel.titleImage is null since you created the _newsModel object without supplying the parameters.

Solution:

The solution will be to delete this line below from your build method:

      NewsModel _newsModel = NewsModel();

Then you can get your _newModel variable from the _controller.newsModelList list.

Your new UI code containing both solutions should be updated to the code block below:

    class HomeBody extends StatefulWidget {
      @override
      _HomeBodyState createState() => _HomeBodyState();
    }
    
    class _HomeBodyState extends State<HomeBody> {
    
    //
      final _controller = Get.put(Controller());
    
         @override
      Widget build(BuildContext context) {
        return Container(
          child: Obx(() {
        if (_controller.newsModelList == null) {
          return Text('Loading');
        } else if (_controller.newsModelList.isEmpty) {
          return Text('Empty List');
        } else {
          return ListView.builder(
            itemCount: _controller.newsModelList.length,
            itemBuilder: (context, index) {
              final NewsModel _newsModel = _controller.newsModelList[index];
              return MyContainer(
                title: _newsModel.title,
                titleImage: _newsModel.titleImage,
                index: index,
              );
            },
          );
        }
      }),
        );
      }
Raj A
  • 544
  • 9
  • 21
Victor Eronmosele
  • 7,040
  • 2
  • 10
  • 33
  • Hello Victor, Thank you for the response. Your solution did eliminate the null error but yet I'm getting the 2nd error. I have updated the question. Please checkout. I'm waiting for your response. – Raj A May 21 '21 at 11:06
  • Going through now. – Victor Eronmosele May 21 '21 at 12:46
  • Hello @RajA, I've updated the answer with the fix for the second error. Please check it out. – Victor Eronmosele May 21 '21 at 22:46
  • Hello @Victor, I'm so grateful for your efforts and support. Your solution did work. I did so minor edits like removing the [index] from fields and wrapping it with Obx() as Getbuilder wasn't updating the stream. Excellent support and explaination. Thank you so much. – Raj A May 22 '21 at 12:55
  • Glad to know it worked! Thanks for the edit. – Victor Eronmosele May 22 '21 at 14:58
  • The stream is not updating when the document Id is changed dynamically. Can you please review this issue ?https://stackoverflow.com/questions/67660041/stream-not-updating-when-docid-is-changed-dynamically-flutter-firebase-getx – Raj A May 23 '21 at 13:08
1

You should initialize a value to Rxn

Rxn<List<NewsModel>> finalNewsModel = Rxn<List<NewsModel>>([]);

  //change stream like this
 Stream<List<NewsModel>> streamDemo() {
    return FirebaseFirestore.instance
        .collection('news')
        .doc('USA')
        .snapshots()
        .map((ds) {
         var mapData = ds.data();
  List mapList = mapData['list'];
  List<NewsModel> newsModelList = [];
  mapList.forEach((element) {
    newsModelList.add(NewsModel.fromMap(element));
  });
  return newsModelList;
    });
  }

Use GetX builder or Obx

class HomeBody extends StatefulWidget {
  @override
  _HomeBodyState createState() => _HomeBodyState();
}

class _HomeBodyState extends State<HomeBody> {

//
  final Controller _controller = Get.put<Controller>(Controller());

  //
 

     @override
  Widget build(BuildContext context) {
    return Container(
      child: Obx((){
        if (_controller.newsModelList == null) {
          return Text('Loading');
        } else if (_controller.newsModelList.isEmpty) {
          return Text('Empty List');
        } else {
          return ListView.builder(
            itemCount: _controller.newsModelList.length,
            itemBuilder: (context, index) {
              return MyContainer(
                title: _newsModel.title[index],
                titleImage: _newsModel.titleImage[index],
              );
            },
          );
        }
      }),
    );
  }
}
Ersan Kolay
  • 245
  • 1
  • 6