How to pull snapshot key & values into list through Map<>? - firebase

I've been following the official Firebase tutorial for using a real-time database: https://www.youtube.com/watch?v=sXBJZD0fBa4
I am able to pull all the data from the firebase real-time database. However, the method below to do so, provides a list of the data, with no reference to the parent keys (snapshot.key). An ideal scenario would be to have a key property within the Item class (item.key), so I can call upon it directly from the list.
class DatabaseModel {
final itemsRef = FirebaseDatabase.instance.ref().child('/Contents');
Stream<List<Items>> getItemssStream() {
final itemsStream = itemsRef.onValue;
final streamToPublish = itemsStream.map((event) {
final itemsMap = Map<String, dynamic>.from(event.snapshot.value as Map<String, dynamic>);
final itemsList = itemsMap.entries.map((element) {
return Items.fromRTDB(Map<String, dynamic>.from(element.value));
}).toList();
return itemsList;
});
return streamToPublish;
}
}
class Items{
final String item;
final String expiryDate;
final String quantity;
final String user;
Items({required this.item, required this.expiryDate, required this.quantity, required this.user});
//Mapping from real-time database
factory Items.fromRTDB(Map<String, dynamic> data) {
return Items(
item: data['item'],
expiryDate: data['exp'],
quantity: data['qty'],
user: data['user'],
);
}
}

In this code you only use the element.value of each node in your results:
return Items.fromRTDB(Map<String, dynamic>.from(element.value));
If you also want to get the key of each item, you will have to also use element.key in there and pass that to your Items object.
Something like this:
Items.fromRTDB(element.key, Map<String, dynamic>.from(element.value));
...
class Items{
final String key;
final String item;
final String expiryDate;
final String quantity;
final String user;
Items({required this.key, required this.item, required this.expiryDate, required this.quantity, required this.user});
//Mapping from real-time database
factory Items.fromRTDB(String key, Map<String, dynamic> data) {
return Items(
key: key,
item: data['item'],
expiryDate: data['exp'],
quantity: data['qty'],
user: data['user'],
);
}
}

Related

Unhandled Exception: type 'List<dynamic>' is not a subtype of type 'List<imageObject>?' | How to input Array of Objects in Model firebase in flutter

I have been trying to actually get the data from firebase
So the actual data is :
How to get the imageUrl ?
My Try is :
class ProductModel {
String? name;
String? price;
String? discountPrice;
String? discountRate;
String? category;
String? description;
List<imageObject>? image;
ProductModel(
{required this.name,
required this.price,
this.category,
this.description,
this.discountPrice,
this.discountRate,
this.image});
ProductModel.fromMap(Map<String, dynamic> data) {
name = data['name'];
// image = data['imageUrls'][0]['url']; // To get single image i do this
image = data['imageUrls']; // but this is not working
category = data['category'];
description = data['Description'];
price = data['price'];
discountPrice = data['discountPrice'];
discountRate = data['discountRate'];
}
}
class imageObject {
final String public_id;
final String url;
imageObject({
required this.public_id,
required this.url,
});
}
It gives exception :Unhandled Exception: type 'List<dynamic>' is not a subtype of type 'List<imageObject>?'
And to access the first image i am doing,
product.image![0].url,
where product is of type ProductModel
But this is not working
You need to deserialize the imageObject json data as well. For that you need to add the factory imageObject.fromJson() constructor to your imageObject class just like you did for the ProductModel class.
Here's the code you need:
class ProductModel {
String? name;
String? price;
String? discountPrice;
String? discountRate;
String? category;
String? description;
List<imageObject>? image;
ProductModel({
required this.name,
required this.price,
this.category,
this.description,
this.discountPrice,
this.discountRate,
this.image,
});
factory ProductModel.fromJson(Map<String, dynamic> jsonData) => ProductModel(
name: jsonData['name'] as String?,
price: jsonData['price'] as String?,
category: jsonData['category'] as String?,
description: jsonData['Description'] as String?,
discountPrice: jsonData['discountPrice'] as String?,
discountRate: jsonData['discountRate'] as String?,
//Have a good look here to understand how nested list of maps are deserialized
image: (jsonData['imageUrls'] as List<dynamic>?)
?.map((e) => imageObject.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
class imageObject {
final String public_id;
final String url;
imageObject({
required this.public_id,
required this.url,
});
factory imageObject.fromJson(Map<String, dynamic> jsonData) => imageObject(
public_id: jsonData['public_id'] as String,
url: jsonData['url'] as String,
);
}
What we did here is, take the data from imageUrls key as a List and map every individual element thru the json constructor of the imageObject method.
You have to map the recived list to an imageObjects list.
Create a fromMap constructor for your imageObject class
class imageObject {
final String public_id;
final String url;
imageObject({
required this.public_id,
required this.url,
});
imageObject.fromMap(Map<String, dynamic> map) => imageObject(public_id = map['public_id'], url = map['url'] );
}
Use it something like the following:
ProductModel.fromMap(Map<String, dynamic> data) {
name = data['name'];
image = data['imageUrls'].map((map) => imageObject.fromMap(map) );
category = data['category'];
description = data['Description'];
price = data['price'];
discountPrice = data['discountPrice'];
discountRate = data['discountRate'];
}
You may need to do add some casting or do some modifications to the previous code make it work, but this is the general idea.

The argumnet type 'Null Funcion(DataSnapshot)' cnt be assigned to the parameter type 'Future Or <dynamic> Function(DataBaseEvent)'

I have this function that is giving me an error.
the getCurrentOnLineUserInfo function is trying to get read data from the Firebase Database of the current user that is logged in.
The argument type 'Null Funcion(DataSnapshot)' can't be assigned to the parameter of type 'Future Or Function(DataBaseEvent)'
I am following a year-old tutorial, so the issue might be the code is old. I might need new syntax or something.
static void getCurrentOnLineUserInfo() async {
firebaseUser = await FirebaseAuth.instance.currentUser;
String userId = firebaseUser!.uid;
DatabaseReference reference =
FirebaseDatabase.instance.ref().child("user").child(userId);
print("getCurrentOnLineUser info executed!");
print('${firebaseUser!.email}${firebaseUser!.displayName}');
// errors below this
reference.once().then((DataSnapshot dataSnapshot) {
if (dataSnapShot!.value != null) {
userCurrentInfo = Users.fromSnapshot(dataSnapshot);
}
});
}
}
and here is my class that is assigning data. This class is giving no errors
class Users {
String? id;
String? email;
String? phone;
String? name;
Users({this.id, this.email, this.phone, this.name});
Users.fromSnapshot(DataSnapshot dataSnapshot) {
id = dataSnapshot.key!;
var data = dataSnapshot.value as Map?;
if (data != null) {
email = data?["email"];
name = data?["name"];
phone = data?["phone"];
}
}
}
The once method returns a DatabaseEvent, not a DataSnapshot. DatabaseEvent is a class that encapsulates a DataSnapshot AND a DatabaseEventType, to extract the snapshot, you must use DatabaseEvent.snapshot:
reference.once().then((event) {
final dataSnapshot = event.snapshot;
if (dataSnapShot!.value != null) {
userCurrentInfo = Users.fromSnapshot(dataSnapshot);
}
});
Here is another solution I think might do what you want:
// async methods should return a future
static Future<void> getCurrentOnLineUserInfo() async {
firebaseUser = await FirebaseAuth.instance.currentUser;
String userId = firebaseUser!.uid;
DatabaseReference reference =
FirebaseDatabase.instance.ref().child("user").child(userId);
final snapshot = await reference.get(); // you should use await on async methods
if (snapshot!.value != null) {
userCurrentInfo = Users.fromSnapshot(snapshot);
}
}
}
I was following the same old tutorial you mentioned, the #mobdev991 answer is correct and i think the reason why you don't receive data is the class where you are assigning data try this
class Users {
String? id;
String? email;
String? name;
String? phone;
Users({this.id, this.email, this.name, this.phone});
Users.fromSnapshot(DataSnapshot dataSnapshot) {
id = dataSnapshot.key;
email = (dataSnapshot.child("email").value.toString());
name = (dataSnapshot.child("name").value.toString());
phone = (dataSnapshot.child("phone").value.toString());
}
}

Is there a NoSQL alternative to SQLite for flutter?

I am building a standalone(no API calls to MongoDB or Firebase etc.) flutter app for which database management is crucial. Currently, I am storing it as JSON, but it's simply inefficient. I cannot use SQLite because the data are very nested, in fact, JSON is the only way to store my data. So, I am looking for a NoSQL alternative.
This is the data model I want to store.
lib/book_model.dart
#JsonSerializable()
class Book {
/// Do not change this. It's a primary key.
String bookLink;
String authors = null;
String thumbnail = null;
String bookName = null;
List<Chapter> totalChaptersList = [];
///todo: determine datatype
var currentChapter;
String summary = null;
double rating = 0.0;
List<String> genres = [];
Book({
this.bookLink,
this.authors,
this.thumbnail,
this.bookName,
this.totalChaptersList,
this.currentChapter,
this.summary,
this.rating,
this.genres,
});
Book.generateFromSearchBook(SearchBook searchBook) {
this.authors = searchBook.authors;
this.bookLink = searchBook.bookLink;
this.bookName = searchBook.bookName;
this.thumbnail = searchBook.thumbnail;
}
// #override
// String toString() {
// return "<$bookLink , $authors , $thumbnail , $bookName , $summary , $genres , $rating , $totalChaptersList , $currentChapter>";
// }
factory Book.fromJson(Map<String, dynamic> json) => _$BookFromJson(json);
Map<String, dynamic> toJson() => _$BookToJson(this);
}
#JsonSerializable()
class Chapter {
String name = null;
String date = null;
String chapterLink = null;
#JsonKey(defaultValue: false)
bool has_read = false;
List<Page> pages = [];
Chapter({
this.name,
this.date,
this.chapterLink,
this.has_read,
this.pages,
});
// #override
// String toString() {
// return "<$name , $date , $chapterLink , $has_read , $pages>";
// }
factory Chapter.fromJson(Map<String, dynamic> json) => _$ChapterFromJson(json);
Map<String, dynamic> toJson() => _$ChapterToJson(this);
}
#JsonSerializable()
class Page {
String pageLink = null;
int pageNumber = 0;
Page({
this.pageLink,
this.pageNumber,
});
// #override
// String toString() {
// return "<$pageLink , $pageNumber>";
// }
factory Page.fromJson(Map<String, dynamic> json) => _$PageFromJson(json);
Map<String, dynamic> toJson() => _$PageToJson(this);
}
I found these two
Hive - Hive is a lightweight and blazing fast key-value database written in pure Dart. Inspired by Bitcask.
ObjectBox - ObjectBox is a super-fast database storing Dart objects locally.
I use Hive. It's fast and easy to use.you can use hive_generator with json_serializable and freezed to create a HiveObject class.
example:
#HiveType(typeId: 0)
class Person extends HiveObject {
#HiveField(0)
String name;
#HiveField(1)
int age;
}
You can also use hive_box to turn your hive box into a listenable and listen to database changes.
example:
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
class SettingsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: Hive.box('settings').listenable(),
builder: (context, box, widget) {
return Switch(
value: box.get('darkMode'),
onChanged: (val) {
box.put('darkMode', val);
}
);
},
);
}
}

how to display a custom list data on the app page in flutter

1[https://i.stack.imgur.com/yI2Cp.png">]1
i have tried this code to retrieve data from subcollection of the same name(userProducts) stored in many firebase documents.How do i display this data in a container on the app page ? I tried using listiew.builder but it doesnt work.
The first function
static List<products> finalProductsList = [] ;
productsList() async
{
List list_of_products = await Firestore.instance.collection("products")
.getDocuments()
.then((val) => val.documents);
for (int i=0; i<list_of_products.length; i++)
{
Firestore.instance.collection("products").document(
list_of_products[i].documentID.toString()).collection("userProducts").snapshots().listen(CreateListofProducts);
}
}
Second function
CreateListofProducts(QuerySnapshot snapshot)async
{
var docs = snapshot.documents;
for (var Doc in docs)
{
finalProductsList.add(products.fromFireStore(Doc));
}
}
CourseModel
class products {
final String prodId;
final String ownerId;
final String username;
final String price;
final String productname;
final String details;
final String color;
final String composition;
final String washandcare;
final String sizeandfit;
final String shopmediaUrl;
final String id;
final dynamic likes;
products({ this.prodId,
this.ownerId,
this.username,
this.price,
this.details,
this.productname,
this.color,
this.composition,
this.washandcare,
this.sizeandfit,
this.shopmediaUrl,
this.id,
this.likes,});
factory products.fromFireStore(DocumentSnapshot doc)
{
Map data = doc.data ;
return products(
prodId: doc['prodId'],
ownerId: doc['ownerId'],
username: doc['username'],
price: doc['price'],
productname: doc['productname'],
details: doc['details'],
shopmediaUrl: doc['shopmediaUrl'],
color:doc['color'],
composition:doc['composition'],
washandcare:doc['washandcare'],
sizeandfit:doc['sizeandfit'],
likes: doc['likes'],
);
SO if i understand you, you want to get data on multiple collections on multiple documents?
If so... you should use collecionGroup, you can learn how to use here, make sure to adjust your firestore rules.

How to convert a sub collection to list with flutter and firestore?

I have an object Dish who containes a list of ingredients and I want to get them. How can I do?
In Firebase, Dish is a Document and Ingredient is a sub collection. I tried this but it doesn't work.
class Dish{
String name;
DocumentReference reference;
List<Ingredient> ingredients;
Dish.fromMap(Map<String, dynamic> map, {this.reference}){
this.name = map['name'];
this
.reference
.collection("ingredients")
.snapshots()
.listen((QuerySnapshot snap) {
final List<DocumentSnapshot> ingredientsDocuments = snap.documents;
List<Ingredient> ing = [];
for (var i = 0; i < ingredientsDocuments.length; i++) {
ing.add(Ingredient.fromSnapshot(ingredientsDocuments[i]));
}
this.ingredients = ing;
});
}
Dish.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
#override
String toString() => "Dish<$String>";
}
class Ingredient{
final String name;
final DocumentReference reference;
Ingredient.fromMap(Map<String, dynamic> map, {this.reference})
: assert(map['name'] != null),
name = map['name'];
Ingredient.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
#override
String toString() => "Ingredient<$String>";
}
How are you trying to fetch data from Firestore using Dish class? Were you using any asynchronous task(i.e. Future)? What's not working in your implementation? Any errors that you received?
Since I'm unable to run the repro you've provided, here's a sample code that you can try.
List<Ingredient> ingredients;
// call getDocuments() to fetch data from Firestore and add it to the List
Future<void> getDocuments() async {
ingredients = List();
var collection = FirebaseFirestore.instance
.collection('ingredients');
collection.get().then((value) {
value.docs.forEach((element) {
setState(() {
// add the object to the List
ingredients.add(Ingredient(Ingredient.fromMap(element.data())));
});
});
});
}
As for the Object, it can be as simple as this. No need to pass DocumentReference since we'll only be using it to map the data to the Object and be able to add it in the List.
class Ingredients {
var name;
Ingredients(Ingredients document) {
this.documentName = document.getName();
}
dynamic getName() => name;
Ingredients.fromMap(Map<dynamic, dynamic> document)
: name = document['name'];
}
You can check a working sample I've posted in here. It has pagination added, but it should have a similar approach with the code snippets I've shared here.

Resources