Search code examples
android-roomrx-java2

Prevent Room Observable Query Triggering Multiple Times When Multiple Tables Changed


I use observable queries in Android Room to trigger updates that eventually change the UI when the underlying data changes.

Sometimes these queries involve multiple tables, and sometimes a user performs an action that inserts new values into these tables. The inserts are usually done very quickly one after the other (i.e. in less than a one half of a second time span)

Currently, an observer subscribed to updates from Room will be triggered x times where x is the number of tables in the query that were updated.

So if values are inserted into six different tables in the query all within one second of each other, any observer that is observing the query will fire six times.

Is there a way to tell Room; "Only trigger observers a maximum of once every second"? Or, better yet "When you see an update, wait half a second to collect any other updates, and then notify observers of everything at once"?

Here is the Room Query. As you can see multiple tables are involved.

 @Query("SELECT Distinct order_material.* FROM order_material" +
            " JOIN orders on order_material.orderId = orders.id" +
            " JOIN assignment on order_material.id = assignment.orderMaterialID" +
            " JOIN box_inside on assignment.boxInsideID = box_inside.id" +
            " WHERE orders.isActive = 1 " +
            " AND (box_inside.dateCFOrigPacked <= :dateLimitYema OR box_inside.stemLength IS NOT NULL)" +
            " AND assignment.id IN (SELECT id FROM assignment WHERE active = 1" +
            "      AND quantity > 0 AND assignment.id NOT IN "+
            "              (Select assignmentID FROM " +
            "               (Select assignmentID, assignment.quantity as sum_assign, SUM(preparation.quantityPrepared) as sum_prep " +
            "               from preparation "  +
            "               JOIN assignment on preparation.assignmentID = assignment.id"  +
            "               GROUP BY preparation.assignmentID " +
            "               HAVING sum_assign <= sum_prep)))" +
            " Order By order_material.deliveryDate ASC")
    Observable<OrderMaterial[]> getOrderMaterialsReadyToPrepareAsync(Date dateLimitYema);

Here is a repository method observing the query. It gets triggered X times where X is the number of tables that update within a very short timespan.

 public Observable<List<OrderMaterial>> findOrderMaterialsReadyToPrepareAsync(){
        int daysInOneWeek = 7;
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_YEAR, -1 * daysInOneWeek);
        Date limitOneWeekYemas = calendar.getTime();

        return Observable.create(subscribe ->
            assignmentDao.getOrderMaterialsReadyToPrepareAsync(limitOneWeekYemas)
                    .observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io())
                    .subscribe(new Observer<OrderMaterial[]>() {
                        @Override
                        public void onSubscribe(Disposable d) {
                            Timber.v("findOrderMaterialsReadyToPrepareAsync on subscribe");
                            subscribe.setDisposable(d);
                            preparationDisposable.add(d);
                        }

                        @Override
                        public void onNext(OrderMaterial[] materials) {
//                            Timber.v("findOrderMaterialsReadyToPrepareAsync onNext with %s", Arrays.toString(materials));
                            subscribe.onNext(Arrays.asList(materials));
                        }

                        @Override
                        public void onError(Throwable e) {
                            Timber.e(e,"findOrderMaterialsReadyToPrepareAsync onError");
                            subscribe.onError(e);
                        }

                        @Override
                        public void onComplete() {
                            Timber.e("findOrderMaterialsReadyToPrepareAsync onComplete");
                        }
                    })
        );
    }

Solution

  • Is there a way to tell Room; "Only trigger observers a maximum of once every second"? Or, better yet "When you see an update, wait half a second to collect any other updates, and then notify observers of everything at once"?

    As far as I know there is no such a way to communicate with Room. Observable queries (with LiveData, RxJava, Flow) in Room under the hood invokes using of DB's InvalidationTracker (it's like a simple-any-changes-trigger):

    Adds the given observer to the observers list and it will be notified if any table it observes changes.

    More than that, even if you observe (with one of Observables) query with some condition like select * from users where id =:id, you'll be notified after each table's change, even if there is no changes in tables' rows with this specific id.

    You can try add @Transaction to your query (to get just one triggering) but I guess it will not help.

    So I think you shouldn't expect too much intelligence from Room in that aspect. Then RxJava if you use it - is your first candidate for expecting such smart "conversation" (may be debounce operator would be your choice).