Firebase: avoiding READs when updating data - firebase

I am currently setting up my 1st Firebase store and wondering how to best avoid unnecessary read/write costs (as well as how to create a nested structure...). Quite helpful was this answer.
However, if I listen to changes (caused by other persons) of a document, I assume I also get any change of myself again in return. So when using the logic of the todo example for bloc, I update a document. My listener recognizes this and fires an event to re-read the data from the repository.
#override
Stream<TodosState> mapEventToState(TodosEvent event) async* {
if (event is LoadTodos) {
yield* _mapLoadTodosToState();
} else if (event is TodosUpdated) {
yield* _mapTodosUpdateToState(event);
} else if (event is UpdateTodo) {
yield* _mapUpdateTodoToState(event);
}
Stream<TodosState> _mapLoadTodosToState() async* {
_todosSubscription?.cancel();
_todosSubscription = _todosRepository.todos().listen(
(todos) => add(TodosUpdated(todos)),
);
}
Stream<TodosState> _mapTodosUpdateToState(TodosUpdated event) async* {
yield TodosLoaded(event.todos);
}
Stream<TodosState> _mapUpdateTodoToState(UpdateTodo event) async* {
_todosRepository.updateTodo(event.updatedTodo);
}
Since I assume there may be multiple near time changes to a document by the same user in my app, is setting the source option to offline cache for 1min with each write access a proper option or are there better options?
And in case there isn't, can I somehow ensure that the data is sent when the user leaves the app (eg. when bringing another app upfront)?
And is there any overview how to use Firestone with Flutter? Unfortunately the coding examples in Google's documentation are for any language but Dart/Flutter. How would I, for example, set the source option with Flutter (haven't searched for it yet)?

You really can't have it both ways, you would either need to give up realtime or assume that this could generate more costs and as mentioned by #creativecreatorormaybenot you should still be using Listeners to deal with realtime updates.
The comments made in the accepted answer you shared are valid points to take better advantage of listeners with the use of cache and specially pagination, as you are very likely not required to have all of your documents in memory all the time.
Also, listeners are rather unexpensive for small projects, as you appears to be, and you have a free tier of 50.000 reads a day for Firestore, which will include read that come from listeners.

Related

How to update a list automatically using Firestore Stream?

In my app, I am now using a "refresh function" to update a list in Provider. When the user swipe, I call Refreshlist in my provider and with NotifyListeners() it updates my UI just fine. (The UI is linked to the list _myEleves).
I am afraid that users might use this "refresh" button too many times making unnecessary calls and "reads" on firebase and so increasing artificially the number of reads so the costs of Firebase.
Here is the code :
Future<void> refreshEleveList() async {
final DocumentSnapshot<Map<String, dynamic>> docInfo =
await FirebaseFirestore.instance
.collection('familyAccounts')
.doc(_accountEmail.toLowerCase())
.get();
_mesEleves = (docInfo['mesEleves'] as List<dynamic>)
.map((data) => Eleve.fromMap(data))
.toList();
notifyListeners();
}
I have been reading about STREAMS a lot, but I just can't get it right on how to start this stream, the listening to the changes on Firebase inside my PROVIDER file, so that changes will be made to "_myEleves" list.
What I want to do is that each time a change on firebase happens, it updates my list "_myEleves". Is there a simple way to do this ?
My Provider covers the whole app (I use it in the MAIN file). I thought of adding a StreamProvider, but the thing is I don't want this stream to start until user is authentified etc... and userInfo is first downloaded.
Right now : when user logs in : it downloads from firebase all necessary info, _mesEleves being one of them (This is for a teacher). Whenever a new student joins the group, it modifies firebase and so it should stream down this info into "myEleves" list on the teacher account.
Anybody can help ?

Flutter + Firebase - Check User Presence

I am trying to remove users from database (Firebase Realtime Database) who are away or have disconnected. I did my search and the only resource I could find related to this was:
https://firebase.google.com/docs/firestore/solutions/presence
The link is a web solution, I have tried to adopt the concept to try to make it work:
#override
void initState() {
super.initState();
checkConnection();
}
checkConnection() {
databaseReference.child('.info/connected').onValue.listen((data) {
if (data.snapshot.value == false) {
return;
}
databaseReference
.child('games')
.child(inviteCode)
.child("players")
.child(playerID)
.onDisconnect()
.remove();
});
}
The above code doesn't seem to work. I have tried testing it on iOS simulator.
I am not using Firebase Authenticator. I am simply adding users directly to the Real Time Database and the structure is as follows:
games { inviteCodehere: { players: {-M_AUmwDhQBzFdPL1lsE: {name: saad, score: 0 } } } }
Would appreciate if someone could guide me. And is there a way I can define the trigger for how long the user is away? If not, what is the default value. Thank you
How do you test if it is working or not?
It can take some time for it to get triggered. Because you also search for something to indicate for how long the user is gone maybe this would help you.
You can make a combination of the onDisconnect event and a periodic is alive timestamp you save to the database each time a user does something in the app.
The RealtimeDatabase SDK for flutter is still in beta so I would recommend the is alive solution and if you need to delete some data if someone is gone for a specific time. Create a cloud function and query the timestamps older than a specific time and delete the data related to them.

Why does flutter firestore plugin not worry about closing its sinks (snapshot streams)?

In the flutter firestore codebase you can find a comment about the stream it creates when you run snapshots() on a query.
// It's fine to let the StreamController be garbage collected once all the
// subscribers have cancelled; this analyzer warning is safe to ignore.
StreamController<QuerySnapshotPlatform> controller; // ignore: close_sinks
I want to wrap my resulting snapshot streams with a BehaviorSubject so I can keep track of the latest entry. This is useful when I have one stream that is at the top of a page that I want to be consumed through different widgets farther down in my tree without reloading the stream each time. Without keeping track in a BehaviorSubject or elsewhere if a new widget starts listening to that stream it does not get the most recent information from Firestore as it missed that event.
Can I also not worry about closing the behavior subject I am going to create as it will be garbage collected when there are no more listeners? Or is there another way to achieve what I am wanting?
I'm picturing code like this:
final snapshotStream = _firestore.collection('users').snapshots();
final behaviorSubjectStream = BehaviorSubject();
behaviorSubjectStream.addStream(snapshotStream);
return behaviorSubjectStream;
This will get a complaint that I don't close the behaviorSubjectStream. Is it ok to ignore?
That depends on how you listen to the subject.
From what you describe, it sounds safe to ignore the hint. When the subscriptions that listen to the subject are cancelled, the subject will be cancelled as well (when the garbage collector finds it).
There are situations where you have a subscription that is still listening, but you want the subject to stop emitting. In that case you will need to close() the subject.
You can test that the subject is correctly cancelled by adding
behaviorSubjectStream.onCancel = () {
print("onCancel");
};
Then you can test it by playing around with your app.

How to update the same document with a read from the same collection in an onUpdate function

I'm trying to update the same document which triggered an onUpdate cloud function, with a read value from the same collection.
This is in a kind of chat app made in Flutter, where the previous response to an inquiry is replicated to the document now being updated, for easier showing in the app.
The code does work, however when a user quickly responds to two separate inquiries, they both read the same latest response thus setting the same previousResponse. This must be down to the asynchronous nature of flutter and/or the cloud function, but I can't figure out where to await or if there's a better way to make the function, so it is never triggering the onUpdate for the same user, until a previous trigger is finished.
Last part also sound a bit like a bad idea.
So far I tried sticking the read/update in a transaction, however that only seems to work for the single function call, and not when they're asynchronous.
Also figured I could fix it, by reading the previous response in a transaction on the client, however firebase doesn't allow reading from a collection in a transaction, when not using the server API.
async function setPreviousResponseToInquiry(
senderUid: string,
recipientUid: string,
inquiryId: string) {
return admin.firestore().collection('/inquiries')
.where('recipientUid', '==', recipientUid)
.where('senderUid', '==', senderUid)
.where('responded', '==', true)
.orderBy('modified', 'desc')
.limit(2)
.get().then(snapshot => {
if (!snapshot.empty &&
snapshot.docs.length >= 2) {
return admin.firestore()
.doc(`/inquiries/${inquiryId}`)
.get().then(snap => {
return snap.ref.update({
previousResponse: snapshot.docs[1].data().response
})
})
}
})
}
I see three possible solutions:
Use a transaction on the server, which ensures that the update you write must be based on the version of the data you read. If the value you write depends on the data that trigger the Cloud Function, you may need to re-read that data as part of the transaction.
Don't use Cloud Functions, but run all updates from the client. This allows you to use transactions to prevent the race condition.
If it's no possible to use a transaction, you may have to include a custom version number in both the upstream data (the data that triggers the write), and the fanned out data that you're updating. You can then use security rules to ensure that the downstream data can only be written if its version matches the current upstream data.
I'd consider/try them in the above order, as they gradually get more involved.

Is it good idea to use admin.database().ref().on('child_added') in cloud functions?

I'm creating a general purpose queue on firebase cloud functions to run huge list of task. I was wondering if i can use .on('child_added') to get new task pushed to the queue.
Problem i was getting is that my queue is breaking in a middle randomly after 10 mins or sometimes 15 mins.
admin.database().ref('firebase/queues/').on('child_added', async snap => {
let data = snap.val();
console.log(data);
try {
await queueService.start(data);
} catch (e) {
console.log(e.message);
}
snap.ref.remove();
});
Or shall i go back to use triggers?
functions.database.ref('firebase/queues/{queueId}').onCreate(event => {
return firebaseQueueTrigger(event);
});
You can use child_added in cloud functions if you just want to retrieve data from the database.
onCreate() is a database trigger, that is triggered whenever new data is added to the database.
more info here:
https://firebase.google.com/docs/functions/database-events
so when new data is added to the database, at the location provided onCreate() will trigger. Both can also be used in cloud functions
The problem is not in using child_added, it is in keeping an active on(... in Cloud Functions. The on(... method attaches a listener, which stays attached until you call off(). This by nature conflicts with the ephemeral nature of Cloud Functions, which are meant to have a trigger-execute-exit flow. Typically if you need to read additional data from the database in your Cloud Function, you'll want to use once(.... so that you can detect when the read is done.
But in your specific case: if you're creating a worker queue, then I'd expect all data to come in with event.data already. Your functions.database.ref('firebase/queues/{queueId}').onCreate(event is essentially the Cloud Functions equivalent of firebase.database().ref('firebase/queues').on('child_added'.

Resources