How to update an inventory with offline users in Firebase? - firebase

Firebase: How to update an inventory with offline users?
I have an application for Agro built with Flutter and Firebase. The application works as follows:
You have a Food Inventory in the Warehouse
Users go out to feed the animals in offline mode (they are on a farm)
Then they return and the database writes are executed according to the Offline properties of Firebase
When users are online at feeding time, everything works perfectly, but when feeding in offline mode I have a problem specifically updating the inventory, since the offline user cached inventory may be different than the real inventory (either because new foods have been introduced or because of online updates from other users).
The way I am writing the data to Firebase (using the BLOC pattern) is as follows:
Future<bool> actualizarCantidad(String idAlimento, double cantidadActualizada) async {
try {
db.child('alimento/$idAlimento').update({ "cantidad": cantidadActualizada});
} catch (e) {
print(e);
}
return true;
}
The function where the inventory is read and the update of the database is ordered is the following:
Future<void> _submit() async {
_alimento = await alimentoBloc.cargarAlimento(alimentar.idAlimento);
//To read the inventory for the specific food type (alimentar.idAlimento)
final _cantAlimento = _alimento.cantidad - _consumoTotal;
//_alimento.cantidad is refered to the inventory
//_consumoTotal is the quantity to reduce (eaten food)
_alimentoBloc.actualizarCantidad(alimentar.idAlimento, _cantAlimento);
//Use the BLOC and PROVIDER pattern to Update the Inventory with a new Quantity (_cantAlimento)
}
What I would like to do in Firebase is that instead of assigning _cantAlimento quantity to Inventory, execute something like "decrease _consumoTotal of the number in Inventory" and that way it would not matter if the user is Offline or Online. Is this possible?
Another alternative that I have reviewed is to use Transactions to ensure that you are using the latest data, but transactions are lost when the user is offline so it is not a possibility.
How could I update the Inventory in the correct way, considering that my users are often offline?

Since a few months ago Firebase Realtime Database supports a ServerValue.increment() operation that can be used to atomically increment/decrement a value in the database, even when the client is not connected to the servers.
This new method also just landed in version 4.1 of the FlutterFire firebase_database plugin's ServerValue class. If you have issues with it, I'd file a bug or leave a comment on the feature request.

Based on #puf's answer, the procedure to use ServerValue.increment() to update an inventory or a number in Firebase is as follows:
db.child('alimento/$idAlimento/cantidad')
.set(ServerValue.increment(-consumo.round()));
It's important to note that ServerValue.increment() only support integers (until now, it doesn't support doubles). If you have a double number, you should round it up and you can try to use an smaller unit (grs instead Kgs in my case).

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 ?

How to write a log in Firebase?

My App records food consumption in two places in the database and then discounts the inventory. When reviewing, the two records are being done correctly, but the inventory is not updating to the correct value. I would like to see a log of whether there was an error or how the inventory is being updated (I suspect that sometimes it is discounted twice), but I do not know how to do that since I cannot see the print in console (App is in Alpha tests with remote users).
Is there any way I can see or register what happens in the database (log)?
I'm using .push().set to write the two records in Firebase and I'm using ServerValue.increment to update the inventory in this way.
Future<bool> descontarAlimento(String idEmpresa, String idAlimento, double consumo) async {
try {
db.child('bodega/alimento/$idAlimento/cantidad')
.set(ServerValue.increment(-consumo.round()));
} catch (e) {
print(e);
}
return true;
}
I'm considering to add one line in the write as
db.child('logs').push().set('Inventory discounted in $consumo')
and one line following print(e) as
db.child('errors').push().set('Inventory error $e'), but, I'm not sure if it is a good idea.
What's the best way to view or record a "write operation log" in Firebase?
The Firebase Realtime Database does not keep a user-accessible log of operations, as that would become cost prohibitive quickly.
Writing a log from the client is definitely a valid option, as is adding a Cloud Function to do this replication for you.
If you can reasonably reproduce the problem, I'd recommend also running the database profiler to see if it shows any unexpected operations.

I want to sync user contacts to firebase firestore in one go

I am building chat application somewhat like whatsapp. I want to show registered app users list from user's device contact list while creating new group. Now in order to do that I have to compare each and every contact number with firebase firestore users. And any normal user can have more than 500 contacts in device. And moreover firestore has limitation to for querying the db so I can not compare more than one number at a time, the whole process takes almost 6-7 minutes as well as each read operation costs financially.
How can I overcome with this situation, or what is the better way to deal with this particular scenario?
You can store the contacts of the user on device and only send them to firestore as backup. You can then sync your local database with firestore on app start.
The operations you need are not possible to be robust in firebase. Even then if you want to do a search in firebase data, you need to use 3rd party search solution like Elastic Search with your firebase data to perform complex searching.
For local database you can use Room library: https://developer.android.com/topic/libraries/architecture/room
For using Elastic Search with Firebase have a look at this utility Flashlight: https://github.com/FirebaseExtended/flashlight .
The OP requested a structure and some code (Swift, Firebase Database) as a solution. I will present two options
If you want to use a Firebase Query to see if the phone numbers exist, a possible stucture would be
users
uid_0
contact_name: "Larry"
contact_phone: "111-222-3333"
uid_1
contact_name: "Joe"
contact_phone: "444-555-6666"
and then the swift code to query for existing numbers
let phoneNumbers = ["111-222-3333","444-555-6666"] //an array of numbers to look for
let myQueryRef = self.ref.child("users")
for contactPhone in phoneNumbers {
let queryRef = myQueryRef.queryOrdered(byChild: "contact_phone").queryEqual(toValue: contactPhone)
queryRef.observeSingleEvent(of: .childAdded, with: { snapshot in
if snapshot.exists() {
print("found \(contactPhone)") //or add to array etc
}
})
}
Having queries in a tight loop like this is generally not recommended but it usually works fine for me with low iterations. However, queries have a lot more overhead than .observers.
IMO, a better and considerably faster option is to keep a node of just phone numbers. Then iterate over the ones you are looking for and use .observe to see if that node exists.
phone_numbers
111-222-3333: true
444-555-6666: true
and then the code to see if the ones from the array exist
let phoneNumbers = ["111-222-3333","444-555-6666"] //an array of numbers to look for
let phoneNumberRef = self.ref.child("phone_numbers")
for contactPhone in phoneNumbers {
let ref = phoneNumberRef.child(contactPhone)
ref.observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
print("found \(contactPhone)")
}
})
}
In testing, this second solution is must faster than the first solution.

Queries depending on dataset in Firestore

Recently I have migrated from firebase realtime database to firebase firestore because of the fact that it says the speed of the query depends on the size of the dataset(number of documents) being retreived from collection and not on number of documents in a collection. I checked with varying number of documents in a collection 100, 5000 and 10000 and I was retreiving 20 documents in one query. What I saw was the resulting time for the query increased when I moved from 100, 5000 and 10000 documents in a collection. Why is this happening ?
Is it because firestore is in beta ?
Querying on android (collection with 21000 docs)
collectionReference= FirebaseFirestore.getInstance().collection("_countries").document("+91").collection("_classes-exams").document(String.valueOf(mItem)).collection("_profiles").document(mOtherUserUid==null?uid:mOtherUserUid).collection("user_data");
collectionReference.document("_content").collection(articlesOrQuestions)
.orderBy("mTimeStampAsDate", Query.Direction.DESCENDING).limit(20).get().addOnCompleteListener(mCompleteListener)
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
});
Image of android monitor when querying the above collection reference: https://i.stack.imgur.com/QZaVX.jpg
You can see the query took almost one minute by looking at the heap(after one minute memory didn't changed much and remained constant and there is sudden spike in network section after 1 minute by which you can infer onComplete is called). What is happening between calling 'get()' function and 'onComplete' callback. This doesn't happen when querying small collections. and why the query on web is fast but slow on android ?
Link to jsbin: http://jsbin.com/fusoxoviwa/1/edit?html,css,js,console,output
Did you write these collections from the same Android client that's now loading a subset of documents from them? If so, that would explain.
The client-side cache in that case will contain information about all docs, and your app is spending time churning through that information.
If you try on a "clean client", it won't have any information cached and it should only spend time on documents that the client is requesting (or has requested before).
The behavior you're currently seeing should improve before Firestore exits beta, since the index will become more efficient, but also because it'll get some form of GC.

How to structure data in Firebase to avoid N+1 selects?

Since Firebase security rules cannot be used to filter children, what's the best way to structure data for efficient queries in a basic multi-user application? I've read through several guides, but they seem to break down when scaled past the examples given.
Say you have a basic messaging application like WhatsApp. Users can open chats with other groups of users to send private messages between themselves. Here's my initial idea of how this could be organized in Firebase (a bit similar to this example from the docs):
{
users: {
$uid: {
name: string,
chats: {
$chat_uid : true,
$chat2_uid: true
}
}
},
chats: {
$uid: {
messages: {
message1: 'first message',
message2: 'another message'
}
}
}
}
Firebase permissions could be set up to only let users read chats that are marked true in their user object (and restrict adding arbitrarily to the chats object, etc).
However this layout requires N+1 selects for several common scenarios. For example: to build the home screen, the app has to first retrieve the user's chats object, then make a get request for each thread to get its info. Same thing if a user wants to search their conversations for a specific string: the app has to run a separate request for every chat they have access to in order to see if it matches.
I'm tempted to set up a node.js server to run root-authenticated queries against the chats tree and skip the client-side firebase code altogether. But that's defeating the purpose of Firebase in the first place.
Is there a way to organize data like this using Firebase permissions and avoid the N+1 select problem?
It appears that n+1 queries do not necessarily need to be avoided and that Firebase is engineered specifically to offer good performance when doing n+1 selects, despite being counter-intuitive for developers coming from a relational database background.
An example of n+1 in the Firebase 2.4.2 documentation is followed by a reassuring message:
// List the names of all Mary's groups
var ref = new Firebase("https://docs-examples.firebaseio.com/web/org");
// fetch a list of Mary's groups
ref.child("users/mchen/groups").on('child_added', function(snapshot) {
// for each group, fetch the name and print it
String groupKey = snapshot.key();
ref.child("groups/" + groupKey + "/name").once('value', function(snapshot) {
System.out.println("Mary is a member of this group: " + snapshot.val());
});
});
Is it really okay to look up each record individually? Yes. The Firebase protocol uses web sockets, and the client libraries do a great deal of internal optimization of incoming and outgoing requests. Until we get into tens of thousands of records, this approach is perfectly reasonable. In fact, the time required to download the data (i.e. the byte count) eclipses any other concerns regarding connection overhead.

Resources