Retrieve Data from Realtime Database in Flutter

Viewed 42

I want to retrieve data from a realtime database for a flutter application. My data is built like this.

Firebase Realtime Database Screenshot

I need to loop through this data to display it on the application (ecommerce app, obviously). I have tried and failed in many ways. Currently when trying to get the data I see "Instance of '_Future'" as the message.

class Cart extends StatefulWidget {
  Cart({Key? key}) : super(key: key);

  @override
  State<Cart> createState() => _CartState();
}

class _CartState extends State<Cart> {
  DatabaseReference ref = FirebaseDatabase.instance.ref();

  Object? products;
  List productList = [];
  String displayText = 'Results go here!';

  snapshot() async {
    final snapshot = await ref.child('Products').get();
    productList = [];
    if (snapshot.exists) {
      productList.add(snapshot.value);
      products = (snapshot.value);
      print(snapshot);
      print(snapshot.value);
    } else {
      print('No Data Available');
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: const PreferredSize(
            preferredSize: Size.fromHeight(60), child: MyAppBar()),
        body: Column(
          children: [
            ElevatedButton(
              onPressed: () async {
                // await ref.set({"name": "Tyler"});
                snapshot();
              },
              child: Text("Add Data"),
            ),
            Text("${snapshot()}", style: TextStyle(color: Colors.white))
          ],
        )
      );
  }
}

I also have this data class built from other posts I have seen. I have to admit, I am not entirely sure how to use it.

import 'dart:convert';

class ProductData {
  final int productID;
  final String productCategory;
  final String productDesc;
  final String productName;
  final String productPrice;
  final String productSize;
  final bool productInStock;
  final String productImage1;
  final String productGender;
  final String productImage2;
  ProductData(
      {required this.productID,
      required this.productCategory,
      required this.productDesc,
      required this.productName,
      required this.productPrice,
      required this.productSize,
      required this.productInStock,
      required this.productImage1,
      required this.productGender,
      required this.productImage2});

  ProductData copyWith(
      {int? productID,
      String? productCategory,
      String? productDesc,
      String? productName,
      String? productPrice,
      String? productSize,
      bool? productInStock,
      String? productImage1,
      String? productGender,
      String? productImage2}) {
    return ProductData(
      productID: productID ?? this.productID,
      productCategory: productCategory ?? this.productCategory,
      productDesc: productDesc ?? this.productDesc,
      productName: productName ?? this.productName,
      productPrice: productPrice ?? this.productPrice,
      productSize: productSize ?? this.productSize,
      productInStock: productInStock ?? this.productInStock,
      productImage1: productImage1 ?? this.productImage1,
      productGender: productGender ?? this.productGender,
      productImage2: productImage2 ?? this.productImage2,
    );
  }

  Map<String, dynamic> toMap() {
    return <String, dynamic>{
      'productID': productID,
      'productCategory': productCategory,
      'productDesc': productDesc,
      'productName': productName,
      'productPrice': productPrice,
      'productSize': productSize,
      'productInStock': productInStock,
      'productImage1': productImage1,
      'productGender': productGender,
      'productImage2': productImage2,
    };
  }

  factory ProductData.fromMap(Map<String, dynamic> map) {
    return ProductData(
      productID: map['productID'] as int,
      productCategory: map['productCategory'] as String,
      productDesc: map['productDesc'] as String,
      productName: map['productName'] as String,
      productPrice: map['productPrice'] as String,
      productSize: map['productSize'] as String,
      productInStock: map['productInStock'] as bool,
      productImage1: map['productImage1'] as String,
      productGender: map['productGender'] as String,
      productImage2: map['productImage2'] as String,
    );
  }

  String toJson() => json.encode(toMap());

  factory ProductData.fromJson(String source) =>
      ProductData.fromMap(json.decode(source) as Map<String, dynamic>);

  @override
  String toString() {
    return 'ProductData(productID: $productID, productCategory: $productCategory, productDesc: $productDesc, productName: $productName, productPrice: $productPrice, productSize: $productSize, productInStock: $productInStock, productImage11: $productImage1, productGender: $productGender, productImage2: $productImage2)';
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;

    return other is ProductData &&
        other.productID == productID &&
        other.productCategory == productCategory &&
        other.productDesc == productDesc &&
        other.productName == productName &&
        other.productPrice == productPrice &&
        other.productSize == productSize &&
        other.productInStock == productInStock &&
        other.productImage1 == productImage1 &&
        other.productGender == productGender &&
        other.productImage2 == productImage2;
  }

  @override
  int get hashCode {
    return productID.hashCode ^
        productCategory.hashCode ^
        productDesc.hashCode ^
        productName.hashCode ^
        productPrice.hashCode ^
        productSize.hashCode ^
        productInStock.hashCode ^
        productImage1.hashCode ^
        productGender.hashCode ^
        productImage2.hashCode;
  }
}
1 Answers

Since the data is loaded from Firebase asynchronously, its get() method returns a Future. That's also why you had to declare your snapshot() function as async, which means that you also return a Future.

On its own the rendering code doesn't know anything about Futures, so it renders it by calling its toString() method, which leads to the output you see:

Instance of '_Future'

What you want instead is to wait for the future to resolve, which is just a fancy way of saying that you want to wait for the data to load. An easy way to do that is to use a FutureBuilder, which handles the asynchronous nature of a Future and all possible states it can be in.

That'd look something like:

snapshot() async {
  final snapshot = await ref.child('Products').get();
  productList = [];
  if (snapshot.exists) {
    productList.add(snapshot.value);
    products = (snapshot.value);
  } else {
    print('No Data Available');
  }
  return productList;
}
body: Column(
  children: [
    ElevatedButton(
      onPressed: () async {
        snapshot();
      },
      child: Text("Add Data"),
    ),
    FutureBuilder(
      future: snapshot(),
      builder: (BuildContext context, AsyncSnapshot asyncSnapshot) {
        if (snapshot.hasData) {
          var productList = asyncSnapshot.data! as List;
          return Text(productList.length.toString());
        } else if (snapshot.hasError) {
          return Text('Error: ${asyncSnapshot.error}');
        } else {
          return CircularProgressIndicator(),
        }
      }
    )
  ],
)
Related