What triggers LiveData onChanged()? - android

I'm using Room and in the Dao I have this method:
LiveData<List<Books>> getAllBooks();
In MainActivity I have subscribed to that method from the ViewModel. Changes to the data trigger the onChanged() callback:
viewModel.getAllBooks()
.observe(this, books -> {
Log.d(TAG, "onChanged()");
booksListAdapter.setData(new ArrayList<>(books));
});
What I would like to know is what constitutes an update? When the app first starts I do 100 insertions, each of them changes the database but the onChanged() is not invoked 100 times. Last time I checked it called onChanged() the first time which I think it always calls when starting and then two more calls.
Can I have control over this? For example if I know I will be doing 100 insertions perhaps it would be better if I only got the callback at the end of the insertions.

You don't have control of that. What you can do is use MediatorLiveData and post the value after all insertions. Whenever you update, delete or insert Room knows that there has been change but doesn't know what has been changed. So it just re-queries and sends the results to observing LiveData
Check this blog and mainly section 7. Avoid false positive notifications for observable queries. Author gives pretty good example of MediatorLiveData which is similar to what you are looking for

Related

Room LiveData not updating correctly

I have a room query that selects the orders submitted within the last hour. when I add/edit the orders everything works great.
#Query("SELECT * FROM OrderTable WHERE datetime(delivery_date) > datetime('now','-1 hour','localtime') ORDER BY datetime(delivery_date) DESC")
fun getAllAvailable(): LiveData<List<Order>>
The problem is when I have some orders within the last hours and there is no edit/add in the orders table, orders don't disappear after their time passed the one-hour range!
Let me explain with a simple example:
There is an order with delivery_date of 2021-05-22 14:00, when the time of device is 2021-05-22 14:30 the livedata result show the order and it is okay, when time pass and it is 2021-05-22 15:01 it should remove the order from the result but it doesn't and always show the order until next add/edit occur to the order table and live data updates!!
P.S: I should mention that Room database instance is singleton using hilt.
You are using query to fetch LiveData for last hour but it will not be executed unless there is a change in room database or your activity/fragment's lifecycle is resumed.
Refer this again : Documentation for LiveData
Here is simple sequence:
Database changes
Livedata updates the observers
Observers are executed
In your case, you are implementing the logic in your query, it does not know that it should be executed.
You can use a runnable to achieve what you want. Consider this.

Can we use LiveData without loosing any value?

I would like to use a LiveData for handling kind of notifications, as it is already lifecycle aware, between a custom view and its wrapping fragment. But it seems that a LiveData may loose values : it will only update to its most recent state and also won't fire values during inactive state of its observers.
I've looked at the SingleLiveEvent purpose from Google code samples, but that solution does not seems to be battle tested yet, and the ticket is still open with recent tries to improve the solution.
So I am looking for a simple way to get notified about events, and at the same time not being worried about Lifecycles (that was why I went for LiveData as a first solution), and that could handle multiple observers.
Is there an existing solution for that ? If I try to implement it, it is sure that I will land into at least an anti-pattern.
One easy way (perhaps too easy) is to use callbacks : but the problem is that I need this feature for several callbacks in my component, leading me in a poor architecture. And also, I want a subscribe system, meaning that there could be more than one observer.
One other way, could be to use RxJava and tranform it into a LiveData, with LiveDataReactiveStreams.fromPublisher() : but now the question is whether I will get all values or only the last one. That's the closest solution I could deal with.
As an interesting alternative there could be AutoDispose or RxLifecycle. And an interesting resource I've found : Blog post on LiveData
What are your thoughts, suggestions ?
Also, please notice that I need this communication from a component wrapped into a Fragment (ChessBoard) toward another Fragment (ChessHistory). So they are both lifecycle aware.
It is not ideal, but this does the trick for me:
/**
* This LiveData will deliver values even when they are
* posted very quickly one after another.
*/
class ValueKeeperLiveData<T> : MutableLiveData<T>() {
private val queuedValues: Queue<T> = LinkedList<T>()
#Synchronized
override fun postValue(value: T) {
// We queue the value to ensure it is delivered
// even if several ones are posted right after.
// Then we call the base, which will eventually
// call setValue().
queuedValues.offer(value)
super.postValue(value)
}
#MainThread
#Synchronized
override fun setValue(value: T) {
// We first try to remove the value from the queue just
// in case this line was reached from postValue(),
// otherwise we will have it duplicated in the queue.
queuedValues.remove(value)
// We queue the new value and finally deliver the
// entire queue of values to the observers.
queuedValues.offer(value)
while (!queuedValues.isEmpty())
super.setValue(queuedValues.poll())
}
}
The main problem with this solution is that if the observers are inactive at the time the values are delivered via super.setValue(), then the values will be lost regardless. However, it solves the issue of losing values when several new ones are posted very quickly – which, in my opinion, is usually a bigger problem than losing values because your observer is inactive. After all, you can always do myLiveData.observeForever() from a non-lifecycle-aware object in order to receive all notifications.
Not sure this will be enough for you, but I hope it can help you or give you some ideas about how to implement your own approach.

Android - Room Persistence Library - Access data both synchronously & through observer based on need

Problem:
I am using Room Persistence Library and so far everything is working fine except that there is a data from select query which I need synchronously as I am calling it from a Periodic Job (Work Manager's Worker). I have defined the return type to be LiveData as I am also accessing it for display purposes in UI and so observers are great for that but now I also need the same data in Job.
Code Snippet
#Query("SELECT * from readings ORDER BY date, time ASC")
LiveData<List<Reading>> getAllReadings();
Tried
I have tried the getValue() method in LiveData but it returns null as the data is not loaded in LiveData while making the query.
readingDao().getAllReadings().getValue() // returns null
Possible Solution
There is only one solution that I can think of which is to duplicate the getAllReadings query with a different name and return type (without LiveData) but I don't think this is a clean approach as it increases duplication of code just to get a synchronous return type.
Please let me know if there is any other solution or perhaps some way to synchronously access data from LiveData variable.
You can allow main thread query when you initialize Room DB, but it's clearly not desirable. This will give you the synchronous behavior but will block user interface. Is there a specific reason you want this to be synchronous?
The reason why getValue() is returning null is because Room is querying data asynchronously. You can attach an observer or a callback function to get result when the query is finished. You can display the result to the UI or chain another call for sequential operation etc from there.
I use RxJava to wrap my query request for asynchronous query but I you can also use AsyncTask.

BehaviorSubject vs PublishSubject

I'm trying to get my head around the golden rule (if any) about:
When to use BehaviorSubject ?
and
When to use PublishSubject ?
The difference between them is very clear
There are many kinds of subjects. For this specific requirement, a PublishSubject works well because we wish to continue the sequence from where it left off. So assuming events 1,2,3 were emitted in (B), after (A) connects back we only want to see 4, 5, 6. If we used a ReplaySubject we would see [1, 2, 3], 4, 5, 6; or if we used a BehaviorSubject we would see 3, 4, 5, 6 etc.
(source : How to think about Subjects in RxJava (Part 1))
I have seen that Subject's are used in two contexts (at least), UI context and listener context.
UI context (MVVM as example)
For example here a BehaviorSubject is used, and it's clear why they use Subject and not Observable but I have changed the BehaviorSubject to PublishSubject but the app behavior still the same.
Listener context
Why they make project field a BehaviorSubject and not PublishSubject ?
The main difference between PublishSubject and BehaviorSubject is that the latter one remembers the last emitted item. Because of that BehaviorSubject is really useful when you want to emit states.
Why they make project field a BehaviorSubject and not PublishSubject ?
Probably because they want to be able to retrieve the last emitted project with this method:
#Override public #NonNull Observable<Project> project() {
return this.project;
}
PublishSubject: Starts empty and only emits new elements to subscribers.
There is a possibility that one or more items may be lost between the time the Subject is created and the observer subscribes to it because PublishSubject starts emitting elements immediately upon creation.
BehaviorSubject: It needs an initial value and replays it or the latest element to new subscribers. As BehaviorSubject always emits the latest element, you can’t create one without giving a default initial value.
BehaviorSubject is helpful for depicting "values over time". For example, an event stream of birthdays is a Subject, but the stream of a person's age would be a BehaviorSubject.
Publish Subject: Here, if a student entered late into the classroom, he just wants to listen from that point of time when he entered the classroom. So, Publish will be the best for this use-case.
Behavior Subject: Here, if a student entered late into the classroom, he wants to listen the most recent things(not from the beginning) being taught by the professor so that he gets the idea of the context. So, here we will use Behavior.
The difference on BehaviourSubject and PublishSubject relies on how long they keep the data they captures, in instance the PublishSubject only keeps the data available at moment and keeps updating on every entry while BehaviourSubject keeps the last data inserted, so you may use for example to confirm password on a signup form and as an example for PublishSubject, performing a search and it has to update the data constantly in order to give accurate results and there's no too much necessity to compare data that are being inserted.
As reference i leave this two photos from http://reactivex.io/documentation/subject.html
PublishSubject
BehaviourSubject

Trigger Observable on Subject's onNext and Share Result

I have a button which when pressed should make the btnSubject's onNext fire and make an API call in an Observable created in my ViewModel like so:
val apiObservable =
btnSubject.flatMap{ apiService.getSomething()
.toResponseOrErrorObservable()
.subscribeOn(Schedulers.io())}
Then I can reuse this observable to create two more, which are then subscribed to from my view allowing me to keep the logic in my ViewModel like so:
val successObservable = apiObservable.filter{ it.isSuccess() }
val failureObservable = apiObservable.filter{ it.isFailure() }
So apiObservable is triggered by the btnSubject.onNext() being called.
The view is then updated because it's listening to the successObservable and failureObservable
Is this possible? Perhaps with a .share() on the apiObservable?
UPDATE
I added the share operator and all observables emitted items when first subscribing. Even the filters didn't stop it... I must be missing something obvious
There might be a few way to do that.
As you have written, using share() operator multiplies output to many Subscribers. However, you have to be careful, that you also have to call connect() to turn cold Observable into hot one. If calling also replay(), you woudln't need to call connect() many times.
(Source)
However, there is more simple solution: use Jake Wharton's library RxReplayingShare. The author of previous blog post suggests it in his next article

Categories

Resources