I want to return a list of members via a StreamController.
batches collection contains batch details and ids of members assigned to the batch.
So, in-order to get the list of members in a batch, have to loop through batch collection and get the ids of members, then match with members collection and return the matching member data as stream.
final CollectionReference _batchCollectionReference =
Firestore.instance.collection('batches');
final CollectionReference _membersCollectionReference =
Firestore.instance.collection('members');
final StreamController<List<Member>> _membersController =
StreamController<List<Member>>.broadcast();
Stream getMembers(String batchId) { //passing a batch id
_batchCollectionReference
.document(batchId)
.snapshots()
.map((batchSnapshot) => Batch.fromData( //return as Batch type
data: batchSnapshot.data, batchId: batchSnapshot.documentID))
.listen((snapshot) {
List<String> members = snapshot.members; //list of members
members.forEach((member) {
var data = _membersCollectionReference
.document(member)
.snapshots()
.map((memberData) => Member.fromData(data: memberData.data)); //return as Member type
_membersController.add(data);
});
});
return _membersController.stream;
}
}
The problem is I couldn't able to push the member data to the StreamContoller.
It says,
The argument type 'Stream<Member>' can't be assigned to the parameter type 'List<Member>'
The stream should contains instance of members; Ex: [[instance of 'Member'], [instance of 'Member'], [instance of 'Member']]
If I got the data like this way, it would be easy to loop and do the other stuff.
I couldn't able fix this issue. Any help would be appreciated.
Firstable when you need to add a list to the stream so convert your map data to a list, just adding toList() at the end of you map as follows:
members.forEach((member) {
var data = _membersCollectionReference
.document(member)
.snapshots()
.map((memberData) => Member.fromData(data: memberData.data)).toList();
And to push the data in the Stream, you need to use sink.add() this can be an example of a function to push data into the stream and the other one to get the values:
final StreamController<List<Member>> _membersController = StreamController<List<Member>>.broadcast();
/// Inputs
Function(List<Member>) get changeMembers => _membersController.sink.add;
/// Getters
String get members => _membersController.value;
In your case you can do it directly in this way:
_membersController.sink.add(data);
Hope it helps, for more info please check this video or the documentation about streams in dart.
Related
How will i be able to retrieve the field and field values from firebase?
i want to retrieve "total payment" and its value and store it in array. Below is my code for getting the field values but not the field names.
Future _getDataFromDatabase() async {
await FirebaseFirestore.instance.collection("payments").doc(FirebaseAuth.instance.currentUser!.uid).get().then((snapshot)async{
if(snapshot.exists){
setState((){
totalPayments = snapshot.data()!["total payment"].toString();
balance = snapshot.data()!["remaining balance"].toString();
print(totalPayments);
});
}
});
}
snapshot is of type Map<String, dynamic> so if I understand your question correctly you are asking how to cast a map into a list with its keys preserved as string values.
you can get a list of a key-value pairs using something like
IterableZip([snapshot.keys, snapshot.values])
and then you can flatten it into a list using any approach but the shortest would be using expand
so the final code would be:
import 'package:collection/collection.dart';
//... after that you can use
IterableZip([snapshot.keys, snapshot.values]).expand((i) => i).toList()
I am trying to get all documents where the length of the "users" array is less than 2 and where the userId is not present already. I am doing the following query, but it is not executing correctly. What is the problem and how can I fix it? I just want all documents where there is only one entry in the array "users" and where the array does NOT contain the current userId.
await FirebaseFirestore.instance
.collection('rooms')
.where("users"[0], isNotEqualTo: userId)
.where('users'[1], isEqualTo: null)
.get()
.then((snapshot) async {
// If no empty room is found, create room
if (snapshot.docs.isEmpty) {
print("No empty room found, creating new room");
roomId = await createRoom(userId);
return roomId;
}
You can't query individual array elements in Firestore. You can only check whether an array contains a specific item.
It sounds like your array items have a specific meaning, in which case you should create (nested) fields that indicate/name the roles. For example:
participants: {
creator: "uid1",
receiver: "uid2"
}
With that you can then query the nested fields with dot notation:
.where("participants.creator", "!=", "uid1")
.where("participants.receiver", "==", null)
Keep in mind there that the participants.receiver field still has to exist in the latter case and have a value of null. Firestore can't filter in fields that don't exist.
On a project i am working on i list all contacts with checkboxlist. When you tap one it adds to selectedContact list and Database, tap again and it deletes from both so far so good. My problem was when app restarts selected Contacts also zeroed so i used database. I save phone numbers and query all contacts with that phone number and keep adding it to selectedContacts. The problem is even though selectedContact items has the same values as _contacts items selectedContact.contains(c) returns false.
Any ideas?
c is the 'Contact' class object
return ListView.builder(
itemCount: _contacts?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
Contact c = _contacts?.elementAt(index);
//print(snapshot.data[index]);
//selectedContacts.forEach((element) {print(element.phones.first.value);});
//print(selectedContacts.contains(c).toString() +" " +c.phones.first.value);
return CheckboxListTile(
value: selectedContacts.contains(c),
onChanged: (bool value) {
setState(() {
value
? addDb(c)
: deleteDb(c);
});
},
title: Text(c.displayName ?? ""),
secondary: CircleAvatar(child: Text(c.initials())),
);
void addDb(Contact c) {
selectedContacts.add(c);
DatabaseHelper.addContact(c);
}
void deleteDb(Contact c) {
selectedContacts.remove(c);
DatabaseHelper.deleteContact(c);
}
var selectedContacts = List<Contact>();
void importFromDatabase()async{
var import = await DatabaseHelper.importContacts();
import.forEach((element)async {
var contacts = (await ContactsService.getContactsForPhone(element,withThumbnails: false,iOSLocalizedLabels: true)).toList();
selectedContacts.addAll(contacts);
});
}
I have some kind of debugging print statement below which prints selectedContacts and prints if it contains the C
but even though their variables are same it returns false.
EDIT:
Contact class is from contacts_service package
// Name
String displayName, givenName, middleName, prefix, suffix, familyName;
// Company
String company, jobTitle;
// Email addresses
Iterable<Item> emails = [];
// Phone numbers
Iterable<Item> phones = [];
// Post addresses
Iterable<PostalAddress> postalAddresses = [];
// Contact avatar/thumbnail
Uint8List avatar;
List.contains checks the items in the list for strict equality. The Contact class from the referenced package defines equality as two objects having equal values in 16 different fields. If even one of them is different in the object you are checking with, contains will return false. Since you are only saving phone numbers in your database, you are probably not saving all the fields that a Contact object has, so when you recreate that object, it won't "equal" the object it originally referred to.
You need to store the whole object in your database. Relational databases aren't ideal for storing objects with a complex structure, so instead I would recommend using a NoSQL database such as hive, which comes with the added bonus of being able to operate exponentially faster than sqflite due to its use of a memory buffer.
Fortunately, the Contact class defines toMap and fromMap methods, so both storage and retrieval are easy:
// Storage
var box = Hive.box('contactsBox');
var payload = contact.toMap();
await box.put(contact.phones.first, payload);
// Retrieval
var box = Hive.box('contactsBox');
var stored = box.get(phoneNumber);
var contact = Contact.fromMap(stored);
(Note: box.put returns a future, but awaiting it isn't strictly necessary. Storage of data into the memory buffer is synchronous and the future only resolves when the data is successfully written to the file storage. If you don't care about waiting for that to happen and want to access the data immediately, you can skip the await here.)
If I have the following class, how can I save a list of it with Proto DataStore?
data class Tag(
val id: int,
val name: String
)
All guides that I saw were teaching how to save only a single object. Is it possible to have a list of it?
You should consider storing list of content in Room, Even proto-datastore isnt a proper solution to store complex stuff,
If you still want then, I will suggest you to restrict the data stored to 10-15 items
to the code --->
Create your proto file, repeated is used to create list type for Java
message Student {
string id = 1;
string name = 2;
}
message ClassRoom {
string teacher = 1;
repeated Student students = 2; // repeated => list
}
Inside your proto-store,
dataStore.updateData { store ->
store.toBuilder()
.clearStudents() // clear previous list
.setAllStudents(students)// add the new list
.build()
}
if you want example checkout my sample app, read the data/domain layer
https://github.com/ch8n/Jetpack-compose-thatsMine
Hi I am pretty new to Firebase real time database and this is my first project. Sorry if this is a stupid question.
I am saving my data as follows.
firebase database structure:
Now I want to retrieve all parent chat ids on which the student is participating, using the student_id variable.
I tried as per this SO question and this structure database and retrieve data documentation, but its not retrieving values. Anybody have an idea?
I would suggest saving the chatroom IDs your students are in in a separate location. For example:
Path:
“/users/$uid/chatrooms”
Data:
{
0: 350,
1: 423
}
Thus you could retrieve the chat room ids first before getting the chatroom data.
import { initializeApp } from “firebase”;
import { getDatabase, get, set, ref } from “firebase/database”;
const userChatroomIdsRef = ref(db, ‘/users/${uid}/chatrooms‘);
get(userChatroomIdsRef).then(result => {
const chatroomIds = result.val();
if (!(chatroomIds && chatroomIds instanceof Array)) return; // firebase will return null if its an empty array.
const getChatroomInfoPromises = chatroomIds.map(id => get(ref(db, ‘/chat/${id}/${uid}’)).then(result => result.val());
Promise.all(getChatroomInfoPromises).then(chatroomInfoArray => { YOUR LOGIC HERE });
});
Removing/adding students from/to chatrooms would now be simple as you could just change the array of chatroomIds.
const userChatroomIdsRef = ref(db, ‘/users/${uid}/chatrooms‘);
get(userChatroomIdsRef).then(result => {
const oldIds = result.val();
const newChatroomIds = oldIds.filter(id => id !== ID TO DELETE);
return set(userChatroomIdsRef, newChatroomIds)
});
This is of course assuming that you know the uid of your student_id. If you do not know what uid each student_id has, you must must store a reference. I would suggest saving all student info in the “/users/$uid/” directory. Here you could save the studentId so you can programmatically use it.
In all other firebase logic I would try to use the native firebase uid for querying. This will make your life easier.
It’s always good the keep information organized on the database so your logic is simple.
Please check my code for syntax errors; I wrote this on an iPhone.