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

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.

Related

How to sequentially call the Getx controllers

My Task is :
I have an list of orders in firebase in users collection ,
I want to get information of those orders which is in orders collection,
And moment a list of order is updated in the firebase users collection.
The orders list should be automatically updated to get the latest information from the orders collection.
But for that i have to wait until list of orders is fetched from the users collection and only then i can query from orders collection about those orders..
I am stuck here,
And i want to actually understand Getx's bindStream , ever(), and observable variables,and Obx() is used in widget , But what if it is normal variable and i want to listen to it's changes ,how to do that, because Obx() can be only used while you use Widget
So far my code:
controllers.dart
UtilityController utilityController = UtilityController.instance;
CartController cartController = CartController.instance;
OrderController orderController = OrderController.instance;
UserModel.dart
class UserModel {
String? uid;
String? email;
String? name;
bool? isAdmin;
String? password;
List<CartItemModel>? cart;
String? token;
List<String>? orders;
UserModel({this.uid, this.email, this.name, this.isAdmin, this.password, this.cart, this.token, this.orders});
UserModel.fromSnapshot(DocumentSnapshot snapshot) {
uid = snapshot.id;
name = snapshot['name'];
token = snapshot['token'];
cart = _convertCartItems(snapshot['cart'] ?? []);
orders = new List<String>.from(snapshot['orders']);
}
List<CartItemModel> _convertCartItems(List cartFomDb) {
List<CartItemModel> _result = [];
if (cartFomDb.length > 0) {
cartFomDb.forEach((element) {
_result.add(CartItemModel.fromMap(element));
});
}
return _result;
}
}
UtilityController.dart
class UtilityController extends GetxController {
static UtilityController instance = Get.find();
Rx<UserModel> userModel = UserModel().obs;
#override
void onReady() {
super.onReady();
getUserType();
userModel.bindStream(listenToUser());
}
Stream<UserModel> listenToUser() {
return FirebaseFirestore.instance
.collection("users")
.doc(FirebaseAuth.instance.currentUser?.uid)
.snapshots()
.map((snapshot) => UserModel.fromSnapshot(snapshot));
}
OrderController.dart
class OrderController extends GetxController {
static OrderController instance = Get.find();
RxList<OrderModel> orders = RxList<OrderModel>([]);
#override
void onReady() {
super.onReady();
orders.bindStream(getAllOrders());
ever(utilityController.userModel, function); --> I am using this , but i know this is not the correct way
}
function(UserModel userModel) {
getAllOrders();
}
Stream<List<OrderModel>> getAllOrders() {
return FirebaseFirestore.instance
.collection("orders")
.where(FieldPath.documentId, whereIn: utilityController.userModel.value.orders)
.snapshots()
.map((query) => query.docs.map((item) => OrderModel.fromMap(item.data(), item.id)).toList());
}
}
The utilityController.userModel.value.orders is null !!! it's not yet loaded, so all the orders are fetched :(
And even if the orders are changed... But new orders are not fetched from the orders collection
How to get over this?
Consider using like this.
class OrderController extends GetxController {
static OrderController instance = Get.find();
final isLoading = true.obs;
final isDone = false.obs;
var orders = OrderModel([]).obs;
#override
void onInit() async {
await listenForOrders();
super.onInit();
}
Future listenForOrders() async {
isLoading.value = true;
isDone.value = false;
Stream<QuerySnapshot> _userOrders;
_userOrders = getAllOrders();
_userOrders.listen((QuerySnapshot query) {
if (query.docs.isNotEmpty) {
query.docs.forEach((element) {
orders.addIf(!orders.contains(element), OrderModel.fromDocumentSnapshot(element));
});
} else {
isDone.value = true;
}
isLoading.value = false;
});
}
Stream<QuerySnapshot> getAllOrders() {
return FirebaseFirestore.instance
.collection("orders")
.where(FieldPath.documentId, whereIn: utilityController.userModel.value.orders)
.snapshots();
}
}
The best approach is to use the worker functions provided by getx controller like:
ever - is called every time the Rx variable emits a new value.
everAll - Much like ever , but it takes a List of Rx values Called every time its variable is changed. That's it.
once - is called only the first time the variable has been changed.

Flutter-Firebase read from firestore multiple fields including array

Im trying to read all the fields inside my uid1 and uid2 document from firestore. My code crashes because fromJson cant convert imageList from array to List.
Future<List<AppClient>> getClientListResult(List<String> listId) async{
final List<AppClient> clientList = <AppClient>[];
print(listId);
final QuerySnapshot<Map<String, dynamic>> snapshot = await _firestore.collection('clients').where('uid', arrayContainsAny: listId).get();
final List<AppClient> result = snapshot.docs.map((QueryDocumentSnapshot<Map<String, dynamic>> doc) => AppClient.fromJson(doc.data())).toList();
clientList.addAll(result);
return clientList;
}
you can use rxdart to combine two collection from firestore use this plugin rxdart
sample code
StreamBuilder(
stream: CombineLatestStream.list([
firestore
.collection('users')
.doc(data['id'])
.snapshots(),
firestore
.collection('Image')
.doc(data['id'])
.collection('Photos')
.orderBy('timestap')
.snapshots(),
]),
builder: (context, snapshotid) {
// here for the 1st stream
final active = snapshotid.data[0].data();
// here is the for the second stream
final active1 =
snapshotid.data[1].docs[0].data();
Your AppClient class should look something similar to this:
class AppClient {
String address;
String email;
List<String> imageList;
String name;
String price;
double rating;
List<String> uid;
AppClient(
{this.address,
this.email,
this.imageList,
this.name,
this.price,
this.rating,
this.uid});
AppClient.fromJson(Map<String, dynamic> json) {
address = json['address'];
email = json['email'];
imageList = json['imageList'].cast<String>();
name = json['name'];
price = json['price'];
rating = json['rating'];
uid = json['uid'].cast<String>();
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['address'] = this.address;
data['email'] = this.email;
data['imageList'] = this.imageList;
data['name'] = this.name;
data['price'] = this.price;
data['rating'] = this.rating;
data['uid'] = this.uid;
return data;
}
}

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 do I write tests for a transformed stream in Flutter?

I have a Provider which has a method which takes data from Firebase as a stream, transforms it to a list and returns a Stream<List<Model>> . I'm trying to write a test where I want to check if the items in the List are the same as I expect them to be. How can I do that?
My Current Code:
test('getContacts returns a empty list when there is no contact',() async{
when(sharedPreferencesMock.get(any)).thenReturn('uid'); //mock the sharedprefs
documentSnapshot = DocumentSnapshotMock(); //mock documentsnapshot
when(documentSnapshot.exists).thenReturn(true); // this is done to pass the getUidByUsername method
documentReference = DocumentReferenceMock(documentSnapshotMock: documentSnapshot);
documentReference.setData({
'uid':'uid',
'contacts':[] // setting the usename in the data already so that duplicate contact exception is thrown
});
userDataProvider.getContacts().asBroadcastStream().listen((data){
expect(data.length,0);
});
});
And the provider method
#override
Stream<List<Contact>> getContacts() {
CollectionReference userRef = fireStoreDb.collection(Paths.usersPath);
DocumentReference ref =
userRef.document(SharedObjects.prefs.get(Constants.sessionUid));
return ref.snapshots().transform(StreamTransformer<DocumentSnapshot, List<Contact>>.fromHandlers(handleData: (documentSnapshot, sink) async{
List<String> contacts;
if (documentSnapshot.data['contacts'] == null) {
ref.updateData({'contacts': []});
contacts = List();
} else {
contacts = List.from(documentSnapshot.data['contacts']);
}
List<Contact> contactList = List();
for (String username in contacts) {
print(username);
String uid = await getUidByUsername(username);
DocumentSnapshot contactSnapshot = await userRef.document(uid).get();
contactList.add(Contact.fromFirestore(contactSnapshot));
}
sink.add(contactList);
}));
}
Update:
StreamController streamController = StreamController<List<Contact>>();
StreamSink<List<Contact>> sink = streamController.sink;
Stream<List<Contact>> stream = streamController.stream;
stream.listen((List<Contact> list){
expect(list.length,1);
});
userDataProvider.mapDocumentToContact(userCollection, userRef, documentSnapshot, sink);
streamController.close();
Make the lambda function that you currently pass to the StreamTansformer a separate function and test that.
If you want to test the full function there is a Firebase mock package on pub.

Resources