Flutter & Firebase: How to populate an array and then later, return all the contents - firebase

I have been trying to get arrays working in Firebase, and I am aware that there are a lot of references and discussions about this online, and I have read through all of these and none of it works.
First off, the Firebase side. The structure containing the array and two example strings inside it:
Firebase Structure
collection -> document -> fields
userData profileImages URLs (array)
: https://firebasestorage.googleapis.com/v0/b/app-138804.appspot.com/o/jRwscYWLs1DySLMz7jn5Yo2%2Fprofile%2Fimage_picker4459623138678.jpg?alt=media&token=ec1043b-0120-be3c-8e142417
: https://firebasestorage.googleapis.com/v0/b/app-138804.appspot.com/o/jRwscYWLs3872yhdjn5Yo2%2Fprofile%2Fimage_picker445929873mfd38678.jpg?alt=media&token=ec3213b-0120-be9c-8e112632
The first issue I am facing is writing to this array in the database:
Firestore.instance.collection('userData').document('profileImages').updateData({
'URLs': _uploadedFileURL,
});
Whenever I add data to this array, it just overwrites the existing data. I need to be able to keep all the existing data intact and simply add the current new line to the array.
Once this is working, I then need to be able to return all of the strings in this array without needing to know how many of them there will be.
For this part, I basically have nothing at this point. I could show some of the things I have tried based on suggestions from other articles on this, but none of it is even close to working correctly.

im assuming that _uploadedFileURL is a String, and you are updating the property URLs, that's why your data gets overwritten, because you are changing the URLs value to a single string which is _uploadedFileURL. to solve this issue, simply get the current data inside profileImages before commiting the update. like so
final DocumentSnapshot currentData = await Firestore.instance.collection('userData').document('profileImages').get();
Firestore.instance.collection('userData').document('profileImages').updateData({
'URLs': [
...currentData.data['URLs'],
_uploadedFileURL
],
});
and for the second part of your question, all you need is to query for the profileImages
Future<List<String>> _getProfileImages() {
final document = Firestore.instance.collection('userData').document('profileImages').get();
return document.data['profileImages]
}
the result of the get method will be a DocumentSnapshot, and inside the data property will access the profileImages which is a List<String>.

Ok guys and girls I have worked this out. Part 1: appending data to an array in Firebase.
Firestore.instance.collection('userData').document('profileImages').updateDataupdateData({
'URLs':FieldValue.arrayUnion([_uploadedFileURL]),
});
Where _uploadedFileURL is basically a string, for these purposes. Now I have read that arrayUnion, which is super groovy, is only available in Cloud Firestore, and not the Realtime Database. I use Cloud Firestore so it works for me but if you are having issues this might be why.
Now what is extra groovy about Cloud Firestore is that you can similarly remove an element from the array using:
Firestore.instance.collection('userData').document('profileImages').updateDataupdateData({
'URLs':FieldValue.arrayRemove([_uploadedFileURL]),
});
So how to get this data back out again. A simple way I have found to get that data and chuck it into a local array is like so:
List imageURLlist = [];
DocumentReference document = Firestore.instance.collection('userData').document('profileImages');
DocumentSnapshot snapshot = await document.get();
setState(() {
imageURLlist = snapshot.data['URLs'];
});
From here at least you have the data, can add to it, can remove from it and this can be a platform for you to figure out what you want to do with it.

Related

Firebase Realtime Database Overrides my Data at a location even when I use .push() method

Firebase Realtime Database Overrides my Data at a location even when I use .push() method. The little-concrete knowledge I have about writing to Firebase Realtime database is that writing to Firebase real time database can be done in few several ways. Two of the most prominent are the
set() and 2. push() method.
The long story short, push() is used to create a new key for a data to be written and it adds data to the node.
So fine, firebase has being co-operating with me in my previous projects but in this, I have no idea what is going on. I have tried different blends of push and set to achieve my goal but no progress so far.
In the code below, what I want to achieve is 2 things, write to a location chatUID, message and time only once, but write severally '-MqBBXPzUup7czdG2xCI' all under the same node "firebaseGeneratedId1" ->
A better structure is below.
Help with code. Thanks.
UPDATE
Here is my code
The writers reference
_listeningMsgRef = _msgDatabase
.reference()
.child('users')
.child(userId)
.child('chats')
.child(chatUIDConcat);
When a user hits sendMessage, here is the function called
void sendMessage() {
_messageController.clear();
var timeSent = DateTime.now().toString();
//Send
Map msgMap = {
'message': msg,
'sender': userId,
'time': timeSent,
'chatUID': chatUIDConcat
};
//String _key = _listeningMsgRef.push().key;
_listeningMsgRef.child(chatUIDConcat).set().whenComplete(() {
SnackBar snackBar = const SnackBar(content: Text('Message sent'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
DatabaseReference push = _listeningMsgRef.child(chatUIDConcat).push().set(msgMap);
});
}
The idea about the sendMessage function, is to write
chatUID:"L8pacdUOOohuTlifrNYC3JALQgh2+q5D38xPXVBTwmwb5Hq..."
message: "I'm coming"
newMessage: "true"
sender: "L8pacdUOOohuTlifrNYC3JALQgh2"
When it is complete, then push new nodes under the user nodes.
EDIT:
I later figured out the issue. I wasn't able to achieve my goal because I was a bit tensed while doing that project. The issue was I was wanted to write new data into the '-MqBBXPzUup7czdG2xCI' node without overwriting the old data in it.
The solution is straight forward. I just needed to ensure I wrote data in that node as new nodes under it. Nothing much, thanks
Frank van Puffelen for your assistance.
Paths in Firebase Realtime Database are automatically created when you write any data under then, and deleted when you remove the last data under them.
So you don't need to first create the node for the chat room. Instead, it gets auto-created when you write the first message into it with _listeningMsgRef.child(chatUIDConcat).push().set(msgMap)

Flutter Firestore not giving updated data when I add a field to a document from the console

I'm facing an odd bug in Flutter Firestore 2.5.3 where if I add a new field (customer_type) to a document in the Firebase Console, the app fails to retrieve the newly added field and throws an exception:
CastError (type 'Null' is not a subtype of type 'String' in type cast)
But it isn't null. I added the new field with the value from the Firebase Console. It's there in the document. Turns out Firebase is getting the old document data without the newly added field.
I'm using a StreamProvider from Riverpod. Here's the code:
static Stream<Customer?> _watchCustomer(final ProviderRefBase ref) async* {
final auth = await ref.watch(authProvider.last);
if (auth == null) {
yield null;
} else {
final customerRef = _db.collection('customers').toCustomerRef(auth.uid);
await for (var customer in customerRef.snapshots().map((doc) => doc.data())) yield customer;
}
}
What could be going wrong?
EDIT:
All the names, collection & field are correct and so is their type. If I re-install the app or fully clear app storage, the same exact code will work just fine. However, if I just clear the cache it doesn't work. It impedes my workflow when debugging and I'd like to know why it's happening. For additional reference, here is my "Customer" model:
Customer.fromMap(final String documentID, final Map<String, dynamic> map)
: id = documentID,
name = map['customer_name'] as String,
mobile = map['customer_mobile'] as String,
type = map['customer_type'] as String;
It's set to be non-nullable, because I'm establishing a strict schema. But that shouldn't be a concern, cause it isn't null in the database. I've added customer_type to all my documents in the collection. And none of them have the value set to null.
Which could only mean that the map that I get from Firestore, couldn't find the requisite key. Which means I'm still getting the old data (without the new field) from Firestore. What gives? It should give me the updated data when I clear the cache at least.
Because your code is working properly until you add the new field in Firestore console, it means that the problem is with your new field.
The problem is you either added a field in Firestore spelled differently (case sensitive or typo) from your model (class Customer) or the type is not appropriate (e.g. you try to read an int from a String, or you try to read an array from a map, etc.)
Let me know if this does not help?
What I know so far is that the stream returns an immediate local snapshot of the Document before fetching the updated version from the server. Which makes sense; except that if I clear the cache, then it should go to the server directly and not give me any error - which doesn't happen.
I think Firestore might be storing the local snapshot in more places than just the cache, but this is merely speculation on my end.
Currently, I've patched the issue by doing a simple null check in the constructor:
type = map['customer_type'] as String? ?? 'customer'
This fix is, however, inconclusive and I won't be marking this as the answer as I feel a proper explanation of what's happening is due.

Why does Firestore send all results, instead of just the new items when using on_snapshot?

I'm using Firebase's Firestore to store and publish new events.
In the code below, I'm subscribing to a collection and want to be notified when a new items is added (this code is executing on a browser).
When I first connect, I would like to receive a true snapshot. However, once I'm connected to Firestore and have received an initial snapshot, with each new item, I only want to get the udpates, not the whole collection over and over again!
function queryExercise(exercise){
db.collection("exercises").where("exercise","==",exercise).onSnapshot(function(querySnapshot){
querySnapshot.forEach(function(doc){
var d_ = doc.data()
console.log(d_);
...do somethign with d_...
})
})
}
When I publish a new item to the collection, my console is full of all events received earlier...in other words, it is sending me the full snapshot, instead of just the deltas.
Am I doing something wrong or does the API really not support delta updates?
Looks like I needed to read on docChanges:
function queryExercise(exercise){
db.collection("exercises").where("exercise","==",exercise).onSnapshot(function(querySnapshot){
// \/-----this thing
querySnapshot.docChanges().forEach(function(change){
var d_ = change.doc.data()
console.log("Change type:", change.type, d_);
...
});
})
}
From https://firebase.google.com/docs/firestore/query-data/listen
That's the way Firestore queries work. If you don't provide a filter for which documents you want in a collection, you will get all the documents in that collection. The only way to change this behavior is to provide a filter in your query using a where clause.
It sounds like you have a thought in mind about what makes for a "new" document in your collection. You will need to represent that using some field in the documents in your collection. Usually this will be a timestamp type field that's added or modified whenever a document is created or changed. This will be part of your where clause that determines what's "new". Use this field as a filter to find out what's new.

Correctly updating the same object on create trigger in firebase realtime DB trigger

I have a firebase realtime database trigger on a create node. my need is to update a property based on some condition in the create trigger for the same object. The way i am doing currently is below:
exports.on_order_received_validate_doodle_cash_order = functions.database.ref("/orders/{id}")
.onCreate((change, context) => {
console.log("start of on_order_received_deduct_doodle_cash")
const orderId = context.params.id
const order = change.val();
var db = admin.database();
const orderRef = db.ref('orders/')
return orderRef.child(orderId).update({"_verifiedOrder": true})
})
As you can see i am getting order id from context and then querying object again and updating it. My question is do i need to do this circus or can i just update it without querying again?
Generally it looks good. Just some small feedback to make you feel more confident about being on the right track.
Call the parameter snapshot instead of change because the parameter name change only make sense for the onUpdate event trigger.
You do not need to log that you're entering the function. Because entering and leaving the function is automatically logged by Firebase also.
You can remove the order variable that is unused.
You are actually not "querying" the object again. Making a reference to a node in the database doesn't make any network call itself. Not until you subscribe to receiving data. So doing orderRef.child(orderId) is not a query, it's just a database reference.
You can use the snapshot's own reference attribute to shorten your code a bit... effectively throwing away almost all code :-)
So your code code look like this instead. It is doing the exact same thing, just shorter. It was also correct from the beginning.
exports.on_order_received_validate_doodle_cash_order = functions
.database
.ref("/orders/{id}")
.onCreate((snapshot) => {
return snapshot.ref.child("_verifiedOrder").set(true);
});
But as mentioned in my comment above, you are effectively just setting a flag that is confirming that data was saved (or rather: confirming that the function was triggered). You might want to add some logic in there to check whether the order can be placed or not and then set the verified flag to true or false depending on that. Because with the logic of the implementation, all orders will have the value _verifiedOrder set to true, which is a waste of storage in your database.

Flutter Firebase Children Count

I'm currently creating a dashboard application for my main application, this dashboard is able to display in charts the demography of the users that uses the app. I use Firebase Database as the backend. The JSON tree of my DB is as shown below. My question is, how do I get the amount of data with a specific value of a key? Example: the number of children with the value 'Pria' for the key 'jk' is 2.
My Backend JSON Tree:
So far, I'm able to get all of the data using:
DatabaseReference itemRef = FirebaseDatabase.instance.reference().child('data_pengguna');
And I've also tried the codes below, but it doesn't seem to work:
int jmlPria;
FirebaseDatabase.instance
.reference()
.child('data_pengguna')
.orderByChild('jk')
.equalTo('Pria')
.once()
.then((onValue) {
Map data = onValue.value;
jmlPria = data.length;
});
But I haven't successfully filtered the data and put it inside a variable, can anyone help me?
Many thanks in advance.
That last snippet looks correct, jmlPria should have the number of children.
But the value of jmlPria will only be set to the latest value inside the then() callback. Make sure that Text($jmlPria) is inside the then() callback. Outside of that, jmlPria will not have the correct value.
Also see Doug's great blog post on asynchronous programming.

Resources