Flutter Read Realtime Database - flutter

I'm trying to count the number of messages in my Firebase Realtime Database but I can't read further than the first 'messages' branch.
I use this command to display the exact number of conversations I've created.
FirebaseDatabase.instance.reference().child('messages').once().then((DataSnapshot snapshot){
print(snapshot.value.length);
});
He looks good to me 14 but I need to do a count of each message created in each branch (chat room).
To understand the tree structure:
messages
----------id chat room
-------------id message

One way to do this is to use the onChildAdded stream. This stream is called with each child node of the location on which you listen, so one level lower in the JSON tree.
It would look something like:
FirebaseDatabase.instance.reference().child('messages').onChildAdded.listen((Event event) {
print(event.snapshot.value.length);
});
When you first call onChildAdded.listen, the length for each existing child node will be printed. Then afterwards, if you add a new child node (directly) under messages, the length of that will be printed too.

Related

Firebase: How many snapshot listeners is my query causing?

Firebase has a limit of 100 snapshot listeners per client. I have a Screen called "MyChats" which displays all the chats a user has and wanted to know the following:
Is my below function, which gets the data for the screen, counting as just a single listener?
Would grabbing the latest message for each chatroom (to list it in a preview, similar to Whatsapp, FB Messenger and other chat applications) affect my number of listeners?
firestore
.collection("chats")
.where("membersArray", "array-contains", currentUser.uid)
.where("deletedAt", "==", null)
.orderBy("lastMessage.date")
.startAt(new Date())
.onSnapshot(...);
Is my below function, which gets the data for the screen, counting as just a single listener?
You are calling onSnapshot() only once so that's only 1 listener.
Would grabbing the latest message for each chatroom affect my number of listeners?
If you are referring to above code, that's just a single query with 1 listener. If you individually add a listener for each document then that'll be N listeners.
const col = db.collection("chats");
// 1 listener irrespective of number of documents in collection
col.onSnapshot(...)
// 3 listeners, as they are assigned separately
col.doc("1").onSnapshot(...)
col.doc("2").onSnapshot(...)
col.doc("3").onSnapshot(...)

Caching strategies for a chat channel app

I need to develop a sort of chat channel with fairly simple requirements:
Each message should be shown along with the username and avatar of the author
Each message can have multiple comments
Each message should be shown along with its first comment
The first comment (from above) should be shown with its author username / avatar
Now I have designed the mongodb schema to satisfy the above, and fetching the messages is a fairly heavy aggregate command, mainly because I have 3 "lookup" commands:
To fetch the author username/avatar (which is stored just as an id on the message object)
To fetch the first comment (which is stored just as an id on the message object)
To fetch the first comment author username/avatar (again, stored as id on the comment object).
Once the channel is fetched I would like to cache it, reading around I found the Fanout on Write - Cache approach quite appealing, it is basically an "active" cache of the most recent X messages, "active" because it will get updated on every new message to the channel (new message will be push and last message will be removed).
The main issues I am "stuck" with are due to the fact that the "Cache" holds the denormalized data (meaning it includes the username/avatar/"first comment" objects and not ids), hence:
What to do when a user changes his username/avatar
What to do when a user delete a message
What to do when a "first comment" of a message is deleted?
Is it a wrong approach to hold the "cache" in its denormalized form? Any better approach to build a performant chat channel?
Thx!

Whats the best practice for object pool pattern in flutter/dart?

Imagine a very simple application with two pages, PostList and PostDetail. On the former page, we show a list of posts, and on the latter, the details of a single post.
Now consider the following scenario. Our user clicks on the first PostItem and navigates to the PostDetail page. We fetch the full post data from the server. The likes_count of this post gets increased by one. Now if our user navigates back to the first page, the PostItem should be updated and show the new likes_count as well.
One possible solution to handle this is to create a pool of posts. Now when we fetch some new data from the server, instead of creating a new post object, we can update our corresponding pool instance object. For example, if we navigate to post with id=3, we can do something like this:
Post oldPost = PostPool.get(3)
oldPost.updateFromJson(servers_new_json_for_post_3)
Since the same object is used on the PostDetail page, our PostItem on the PostList page will be updated as well.
Other approaches that do not use a unique "single instance" of our Post objects, across the application, would not be clean to implement and requires tricks to keep the UI sync.
But the ObjectPool approach also has its own problems and leads to memory leaks since the size of the pool gets bigger and bigger over time. For this problem to get solved we need to manually count the number of references for each pool object instance and discard them when this count is equal to zero. This manual calculation is full of bugs and I was wondering if there are any better ways to achieve this.
You can also solve this by using streams and StreamBuilders. First you create the stream and populates it with the initial data fetched from the API:
I like to use BehaviorSubject from rxdart but you can use normal streams too.
final BehaviorSubject<List<Post>> postsStream = BehaviorSubject<List<Post>>()..startWith(<Post>[]);
On the constructor body or initState function you would fetch the data and add it to the stream.
PostPage() {
_fetchPosts().then((posts) => postsStream.add(posts));
}
You can now subscribe to changes on this postsStream in both pages with StreamBuilder. Any update you need to do you would emit a new (updated) List<Post> to the stream triggering a rebuild on any StreamBuilder subscribed to the stream with the new and updated values.
You can latter dispose the StreamController.

Firestore How to access a value from a snapshot in a Document Snapshot stream

I'm new to dart/flutter and am building an app right now that keeps track of some data on each user in a firstore database. I have a 'Users' collection which contains a document for each user, and one of the fields in the user document is the "UID" received through firebase_auth.
That being said, to make sure I have access to the latest copy of a user document, I hold a Stream. I want to somehow access the "UID" field from the latest snapshot in the stream and then do some other operations with it.
Is there anyway to do this? Am I using/understanding streams incorrectly?
If you only ever need the UID to build other widgets, you can simply use a StreamBuilder which will rebuild its children whenever a new value is emitted from the stream (which you get a copy of). However, if you need to access the latest UID at some arbitrary point of time, check out RxDart's BehaviorSubject which provides constant-time synchronous access to the latest emitted value, if one exists. It is very helpful for handling state in general.

How to get index of new object on .childMoved

I am using an ordered query of the Firebase real-time database. I have a .childMoved listener on the query and when someone's index in the ordered list changes my listener gets fired. However there doesn't seem to be a way to know what the new index of the object is.
rtdb.child(refString).queryOrdered(byChild: "queuePosition")
.observe(.childMoved, with: { snapshot in
// Do something here with snapshot data
}) { error in
// error
}
How can I find out where the object should be moved to? Or should I just do sorting on the client?
The Firebase Database doesn't expose indexes, since those don't scale well in a multi-user environment. It does have an option to pass the key of the previous sibling of the node with observe: andPreviousSiblingKey.
With this key you can look up the sibling node, and move the child node after that.