How to write a log in Firebase? - 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.

Related

How to set a field for every document in a Cosmos db?

What would a Cosmos stored procedure look like that would set the PumperID field for every record to a default value?
We are needing to do this to repair some data, so the procedure would visit every record that has a PumperID field (not all docs have this), and set it to a default value.
Assuming a one-time data maintenance task, arguably the simplest solution is to create a single purpose .NET Core console app and use the SDK to query for the items that require changes, and perform the updates. I've used this approach to rename properties, for example. This works for any Cosmos database and doesn't require deploying any stored procs or otherwise.
Ideally, it is designed to be idempotent so it can be run multiple times if several passes are required to catch new data coming in. If the item count is large, one could optionally use the SDK operations to scale up throughput on start and scale back down when finished. For performance run it close to the endpoint on an Azure Virtual Machine or Function.
For scenarios where you want to iterate through every item in a container and update a property, the best means to accomplish this is to use the Change Feed Processor and run the operation in an Azure function or VM. See Change Feed Processor to learn more and examples to start with.
With Change Feed you will want to start it to read from the beginning of the container. To do this see Reading Change Feed from the beginning.
Then within your delegate you will read each item off the change feed, check it's value and then call ReplaceItemAsync() to write back if it needed to be updated.
static async Task HandleChangesAsync(IReadOnlyCollection<MyType> changes, CancellationToken cancellationToken)
{
Console.WriteLine("Started handling changes...");
foreach (MyType item in changes)
{
if(item.PumperID == null)
{
item.PumperID = "some value"
//call ReplaceItemAsync(), etc.
}
}
Console.WriteLine("Finished handling changes.");
}

How to update an inventory with offline users in 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).

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.

Optimize Firebase database design

I am having trouble designing the database of my app. In the app users are allowed to create jobs and then using GeoFire I find people nearby.
This is my design for the jobs so far:
As you can see there are the users and then the workers. After pushing the new job to the users Unique ID (UID) under serviceUsers, I then use geoFire to find the workerUsers that are nearby. I then push the jobs into the UID's of the workerUsers.
Now here are my questions:
I am basically creating copies of these jobs. Once for the person who created it (under serviceUsers) and once for every nearby workerUsers.
Is this inefficient? Should I rather pass some kind of pointer instead of the whole job object to the nearby users?
And here for the more important question: If the design is fine as it is, how would I go on about when the creator of the job deletes it? I would then need to find each job in workerUsers and delete the job with the jobs UID. Does Firebase support queries for this?
Thank you very much in advance!
I am basically creating copies of these jobs. Once for the person who
created it (under serviceUsers) and once for every nearby workerUsers.
Is this inefficient? Should I rather pass some kind of pointer instead
of the whole job object to the nearby users?
Every job should have a UUID which can act as a "pointer" (I'd rather call it a key). Then every user should include a job UUID, not a whole copy, so you can refer to it. I won't completely replicate your use case, but you should get an idea.
{
users: {
exampleUserId: {
jobs: ['exampleUUID']
}
},
jobs: {
exampleUUID: {
name: 'awesome job'
}
}
}
If the design is fine as it is, how would I go on about when the
creator of the job deletes it? I would then need to find each job in
workerUsers and delete the job with the jobs UID. Does Firebase
support queries for this?
It does support it, but you should implement my suggestion from above to do it in a sane way. After this, you can create a cloud function whose job should sound like this: "When a job with given UUID is removed, then go through every user and remove a reference to it if it exists"
exports.checkReferences = functions.database.ref('/jobs/{uuid}').onWrite(event => {
// check information here
if (!event.data.val()) {
// job was removed! get its uuid and iterate through users and remove the uuid from them
}
});

Creating many batches (SysOperation Framework) very quickly doing similar processes - "Cannot edit a record in LastValue (SysLastValue)"?

I have a SysOperation Framework process that creates a ReliableAsynchronous batch to post packing slips and several get created at a time.
Depending on how quickly I click to create them, I get:
Cannot edit a record in LastValue (SysLastValue).
An update conflict occurred due to another user process deleting the record or changing one or more fields in the record.
And
Cannot create a record in LastValue (SysLastValue). User ID: t edit a, Class.
The record already exists.
On a couple of them in the BatchHistory. I have this.parmLoadFromSysLastValue(false); set. I'm not sure how to prevent writing to SysLastValue table.
Any idea what could be going on?
I get this exception a lot too, so I've created the habit of catching DuplicateKeyException in my service operation. When it is thrown, catch it and retry (for a default of 5x).
The error occurs when a lot of processes run simultaneously, like you are doing now.
DupplicateKeyException can be caught inside a transaction so you could improve by putting a try/catch around the code that does the insert in the SysLastValue table if you can find the code.
As far as I can see these are the only to occurrences where a record is inserted in this table (except maybe in kernel):
InventUnusedDimCleanUp.serialize()
SysAutoSemaphore.autoSemaphore()
Put a breakpoint there and see if that code is executed. If so you can add a try/catch with retry and see if that "fixes" it.
You could also use the tracing cockpit and the trace parser to figure out where that record is inserted if it's not one of those two.
My theory about LoadFromSysLastValue: I believe setting this.parmLoadFromSysLastValue(false) does not work since it is only taken into account when the dialog is started, not when your operation is executed. When in batch, no SysLastValue will be used to initialize your data contract as you want it to use the exact parameters you have supplied in your data contract .
It's because of the code calling SysOperationController.savelast() while in batch, my solution is to set loadFromSysLastValue to false in SysOperationController.loadFromSysLastValue() as part of the in batch check:
if (!this.isInBatch())
{
.....
}
//Begin
else
{
loadFromSysLastValue = false;
}
//End

Resources