5
votes

I am trying to retrieve data from Firebase Realtime Database into a list in Flutter using a model. My list is returning as null when I do this. I have read several other posts about using Firebase with Flutter but have not found a clear answer. Here is my current approach (the object is called a 'need', and I am trying to retrieve and display a list of needs):

Model:

import 'package:firebase_database/firebase_database.dart';

class Need {
  final String id;
  final String imageUrl;
  final String caption;
  final String title;

  Need({
    this.id,
    this.imageUrl,
    this.caption,
    this.title,
  });

  Need.fromSnapshot(DataSnapshot snapshot) :
    id = snapshot.key,
    imageUrl = snapshot.value["imageUrl"],
    caption = snapshot.value["caption"],
    title = snapshot.value["postTitle"];

  toJson() {
    return {
      "imageUrl": imageUrl,
      "caption": caption,
      "title": title,
    };
  }
}

Database service with Firebase query:

import 'package:firebase_database/firebase_database.dart';
import 'package:Given_Flutter/models/need_model.dart';

class DatabaseService {

  static Future<List<Need>> getNeeds() async {
    Query needsSnapshot = await FirebaseDatabase.instance
      .reference()
      .child("needs-posts")
      .orderByKey();

    print(needsSnapshot); // to debug and see if data is returned

    List<Need> needs;

    Map<dynamic, dynamic> values = needsSnapshot.data.value;
    values.forEach((key, values) {
      needs.add(values);
    });

    return needs;
  }
}

ListView:

import 'package:flutter/material.dart';
import 'package:Given_Flutter/models/need_model.dart';
import 'package:Given_Flutter/services/database_service.dart';
import 'package:Given_Flutter/need_view.dart';

class Needs extends StatefulWidget {
  static final String id = 'needs_screen';

  @override
  _NeedsState createState() => _NeedsState();
}

class _NeedsState extends State<Needs> {
  List<Need> _needs = [];

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

  _setupNeeds() async {
    List<Need> needs = await DatabaseService.getNeeds();
    setState(() {
      _needs = needs;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: RefreshIndicator(
        onRefresh: () => _setupNeeds(),
        child: ListView.builder(
          itemCount: _needs.length,
          itemBuilder: (BuildContext context, int index) {
            Need need = _needs[index];
            return NeedView(
              need: need,
            );
          }
        )
      )
    );
  }
}

Firebase Realtime Database structure:

Firebase example

What am I doing wrong?

4
What does your debug print output?GrahamD
@GrahamD It is showing an error for this line: Map<dynamic, dynamic> values = needsSnapshot.data.value; Error: The getter 'data' isn't defined for the class 'Query'.most200
@GrahamD I have tried removing that line and the following forEach loop to focus just on whether I am getting data back from Firebase. When I do that, the debug log never prints the snapshot data, even though I have a print line for it. I can't tell if it is because that portion is not being executed or if the snapshot is null. Instead the debug log shows an error "The getter 'length' was called on null.", which makes sense because the needs list being returned to the ListView is empty.most200
Try using needsSnapshot.value instead of needsSnapshot.data.value in the code line: Map<dynamic, dynamic> values = needsSnapshot.data.value; I suggest you put a few more print statements in as strategic points to see what you are actually getting. I don't think you can print a snapshot anyway, you will just get instance of snapshot, you have to print its properties eg. .key, .valueGrahamD

4 Answers

2
votes

Map<dynamic, dynamic> values = needsSnapshot.data.value; is wrong.

Value is the data, so just use Map<dynamic, dynamic> values = needsSnapshot.value;

Also the object your appending to to the List is not converted..

// wrong
Map<dynamic, dynamic> values = needsSnapshot.data.value;
    values.forEach((key, values) {
      needs.add(values);
    });

// correct
Map<dynamic, dynamic> values = needsSnapshot.data.value;
    values.forEach((key, values) {
      needs.add(Need.fromSnapshot(values));
    });

One more thing, I'm not 100% sure on this, but if I declare my List as null I have problems when adding value to it, so I always declare the as empty lists.. So instead of List<Need> needs; I'd use List<Need> needs = [];

Hope you solved it already but if not this should work. I just went through the same problem.. Cheers

2
votes

Try this

needsSnapshot.once().then((DataSnapshot snapshot){
  print(snapshot.value.entries.length);

  for (var val in snapshot.value.entries){
    needs.add(new Need(val.id, val.imageUrl,val.caption,val.title));
    //print(val.value.toString());
  }
    //print(needs.length);
1
votes

Note sure if above worked, same can be achieved as below:

List<Need> yourNeeds=[];
await firestore.collection("Need")
        .getDocuments()
        .then((QuerySnapshot snapshot) {
       snapshot.documents.forEach((f) {
            Needs obj= new Need();
       
        obj.field1=f.data['field1'].toString();
        obj.field2=f.data['field2'].toDouble();

        //Adding value to your Need list
        yourNeeds.add(obj);
});
0
votes

Create a one model for holding object. Create a method in it like below,

  ModelName.fromJson(Map<String, dynamic> json) {
    property1 = json['property1'];
    property2 = json['property2'];
  }

Use the below method for converting firebase data snapshot to model directly.

databaseRef.once().then((DataSnapshot snapshot) {
      for(var data in snapshot.value)
        {
          var model = Model.fromJson(Map<String, dynamic>.from(data));
          logger.i(model.iponame);
        }
    });