10

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?

most200
  • 874
  • 1
  • 8
  • 9
  • What does your debug print output? – GrahamD Apr 29 '20 at 19:08
  • @GrahamD It is showing an error for this line: Map values = needsSnapshot.data.value; Error: The getter 'data' isn't defined for the class 'Query'. – most200 Apr 29 '20 at 20:10
  • @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 Apr 29 '20 at 20:13
  • Try using needsSnapshot.value instead of needsSnapshot.data.value in the code line: Map 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, .value – GrahamD Apr 30 '20 at 07:33

6 Answers6

7

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

Vincenzo
  • 5,304
  • 5
  • 38
  • 96
  • I managed to get the data based on your solution, however, when I return the list, it is empty (because the return takes place before data is added to the list in the forEach()). Do you know what I can do so that the return won't execute until my forEach() finishes? It also tells me that the await before the query does nothing. – Dan Crisan Oct 08 '20 at 08:16
  • @DanCrisan Hi, the problem should be that you haven't declared you method as `async` that's why `await` is not doing anything and it returns an empty `List` it doesn't await result to populate it and returns the empty declared. Let me know if this solved it. Cheers. – Vincenzo Oct 09 '20 at 09:19
  • Hi, my method is definitely declared as async, otherwise I think it would have an error when trying to use await. Instead, I just receive a warning saying it doesn't do anything. If I use something like .once() at the end of the query, await works, however, that transforms my query into a DataSnapshot while I need it to be a query. – Dan Crisan Oct 09 '20 at 09:43
  • @DanCrisan please share some code so I can have a look at it and try to spot the problem ;) – Vincenzo Oct 09 '20 at 09:51
  • I have posted the code here: https://stackoverflow.com/q/64277935/10544887 – Dan Crisan Oct 09 '20 at 10:08
  • @DanCrisan I answered it with corrected code.. I can't test it as I don't have yo r job model etc etc.. but it should work, I presume the code is for device and using the official package, if you need help with the web version using the unofficial `firebase` package let me know.. it changes quite a bit .. cheers – Vincenzo Oct 09 '20 at 10:31
  • Thank You Very Much! You saved my life. – Zujaj Misbah Khan Dec 09 '21 at 16:32
  • @ZujajMisbahKhan glad it helped.. – Vincenzo Dec 09 '21 at 16:55
3

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);
3

Just a simple example to get users from firebase realtime database and read once then store and generate user list from it

  final List<User> list = [];

  getUsers() async {
    final snapshot = await FirebaseDatabase.instance.ref('users').get();

    final map = snapshot.value as Map<dynamic, dynamic>;

    map.forEach((key, value) {
      final user = User.fromMap(value);

      list.add(user);
    });
  }
class User {

  final String name;
  final String phoneNumber;

  const User({
    required this.name,
    required this.phoneNumber,
  });

  factory User.fromMap(Map<dynamic, dynamic> map) {
    return User(
      name: map['name'] ?? '',
      phoneNumber: map['phoneNumber'] ?? '',
    );
  }
}

Burhan Khanzada
  • 945
  • 9
  • 27
0

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);
});
Selim Yildiz
  • 5,254
  • 6
  • 18
  • 28
0
FirebaseDatabase.instance.ref("Need").onValue.listen((event) {

 final data = 
 Map<String, dynamic>.from(event.snapshot.value as Map,);

 data.forEach((key, value) {
     log("$value");
 });
});
Amoo Faruk
  • 63
  • 5
  • Remember that Stack Overflow isn't just intended to solve the immediate problem, but also to help future readers find solutions to similar problems, which requires understanding the underlying code. This is especially important for members of our community who are beginners, and not familiar with the syntax. Given that, **can you [edit] your answer to include an explanation of what you're doing** and why you believe it is the best approach? – Jeremy Caney Aug 20 '22 at 01:07
-1

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);
        }
    });
Dharman
  • 30,962
  • 25
  • 85
  • 135
Rushabh Shah
  • 680
  • 4
  • 22