6

As for similar questions on this topic and on ChildEventListener, there is no relevant answer, so heres mine.

I have a local SQLite DB which holds all the data, I also have Firebase realtime database which I'm updating with new entries or real time changes across all users. I'm currently doing it with the use of ChildEventListener as follows:

DatabaseReference rootRef = FirebaseDatabase.getInstance().getDatabase().getReference();
    DatabaseReference childRef = rootRef.child("my_root");

    ChildEventListener eventListener = new ChildEventListener()
    {
        ....
    };
    childRef.addChildEventListener(eventListener);

As for functionality, With this code I can get realtime changes on childs, get new entries, deleted childs and everything I need but there is one problem. When this specific activity with the listener loads up, the onChildAdded listener gets called enormous amounts of times for every child on this root, as stated on the documentation:

child_added is triggered once for each existing child and then again every time a new child is added to the specified path

So I though to gain focus on the items that I really need and I have done it with:

rootRef.orderByKey().startAt("-WhatTF123456789")...

But then I have lost my CRUD capabilities because it's listening to the new entries and not all of them.

So I came up with a solution. Keep node with all the changes that has been made to the FireBase database and a node with all the users that have read and made the changes to the local DB to know who needs an update, Then use addChildEventListener to this specific node. But that seems redundant.

What is my options to handle this kind of situation?

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
homerun
  • 19,837
  • 15
  • 45
  • 70
  • Your options are 1) ignore the redundant child nodes, 2) query to start at the first new child node. The second option is of course preferred, since the first downloads too much data. You day "But then I have lost my CRUD capabilities". Can you elaborate what you mean by that? Or better yet, show it in code? – Frank van Puffelen Jan 13 '19 at 14:45
  • @FrankvanPuffelen My code is basically the `ChildEventListener` naked as i shown above, which contains it's implemented methods. When stating that i'm losing my CRUD capabilities it's because when i'm listening to the last child such as: `childRef.orderByKey().limitToLast(1)`, I get Update and Delete callbacks only on this specific last child. I want to know about all of them on the node, so i won't have to use 1st option. Or are you talking about something else? am i missing something here? – homerun Jan 14 '19 at 07:36

3 Answers3

5

The onChildAdded listener gets called enormous amounts of times for every child on this root.

As you already mentioned and as the docs states, this is the expected behaviour. Usually, is not recommended to attach a ChildEventListener on a node (root node) that contains huge amount of data. Please be careful about this practice because when downloading large amount of data, you can get erros like: OutOfMemoryError. This is happening because you implicitly download the entire node that you are listening to, along with all the data beneath it. That data might be present as simple properties or, as complex objects. So it can be considered a waste of resource and bandwidth. In this case, the best approach is to flatten the database as much as possible. If you are new to NoSQL databases, this practice is called denormalization and is a common practice when it comes to Firebase. For a better understanding, I recommend you take a look at:

Please also note that when you are duplicating data, there is one thing that need to keep in mind. In the same way you are adding data, you need to maintain it. With other words, if you want to update/detele an item, you need to do it in every place that it exists.

I also recommend you to see the last part of my answer from the following post:

It is for Cloud Firestore but same rules apply to Firebase realtime database.

But then I have lost my CRUD capabilities because it's listening to the new entries and not all of them.

Everything in Firebase is about listeners. You cannot get realtime updates for objects within a node, unless you are listening to them. So you cannot limit the results and expect to get updates from objects that you are not listening to. If you need to get updates for all objects within a node, you need to listen to all of them. Because this approach isn't practical at all, you can either use denormalization as explained above or to restrict the results by using queries that can help you limit the amount of data that you get from the database. Regarding your solutions, the second one is much preferred but you can also consider another approach which would be to load data in smaller chunks according to a timestamp property, or according to any other property that you need.

Edit: According to your comment:

Can you please provide tests for each solution (1.denormalization, 2.my solution) examine use of bandwidth and resources and which one is really preferred?

All data is modeled to allow the use-cases that an app requires. Unfortunately, I cannot do tests because it really depends on the use-case of the app and the amount of data that it contains. This means that what works for one app, may be insufficient for another app. So the tests might not be correct for everyone. The denormalization process or your solution is entirely dependent on how you intend to query the database. In the list above, I have added a new resource which is an answer of mine regarding the denormalization tehnique in NoSQL databases. Hope it will also help feature visitors.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • Hey, sorry for the delay I'm currently on vacation, Just letting you know that I've already implemented the solution that uses a node with all the changes, adding, deletes that has been made to the firebase DB. The only listener is to this node, users get RealTime updates and the use of bandwidth is really low. Even though I've already implemented my solution onto the app, can you please provide tests for each solution (1.denormalization, 2.my solution) examine use of bandwidth and resources and which one is really preferred? This is of course for future users encountering this problem. – homerun Jan 19 '19 at 10:11
  • 1
    don't worry my friend, I know how the system work. Here, take your internet points ;) – homerun Jan 19 '19 at 18:24
1

I would make a root node with the name, for example, MaintenanceUpdate. All clients are subscribed to changes here. As soon as MaintenanceUpdate becomes = true, all clients unsubscribe from changes to the main "database". And then (when MaintenanceUpdate = false) are re-subscribed again. At this time you are updating the database.

tim4dev
  • 2,846
  • 2
  • 24
  • 30
1

I have similar requirements, with Firebase and Room, while I've solved it alike this:

public class BaseModel extends BaseObservable implements IDataModel {

    /** Sqlite default PK */
    private int itemId = 0;

    /** Firebase uniqueId */
    @ColumnInfo(name = SqliteBaseHelper.KEY_FIREBASE_UNIQUE_ID)
    protected String uniqueId = null;

    /** Firebase lastSync */
    @ColumnInfo(name = SqliteBaseHelper.KEY_FIREBASE_LAST_SYNC)
    protected long lastSync = 0;

    ...
}

this means, when a local record has a KEY_FIREBASE_UNIQUE_ID which is null and the KEY_FIREBASE_LAST_SYNC is 0, it has to be inserted into Firebase - else it would check, when running a synchronization AsyncTask, if the local or remote record needs to be updated. this is because the main issue is, that when inserting remotely, the ChildEventListener will attempt to synchronize duplicates to the same client - unless having such indicators for the synchronization status in place, locally and remotely. the local primary keys might vary across the clients (depending for how long they were offline and how many records where locally inserted during the offline state), while the synthetic KEY_FIREBASE_UNIQUE_ID is used for identifying them; it's the "key to success".

Martin Zeitler
  • 1
  • 19
  • 155
  • 216