I'm still somewhat new to rxAndroid/rxJava so I'm having a little trouble wrapping my mind around this and if I'm doing everything correctly.
I am using SqlBrite to update a RecyclerView with rows returned from a Cursor. When a db operation is performed on the table, the observable below is responsible for re-querying the data. I want to return a CursorWrapper (ChecklistCursor), but want to make sure I am running the select * query on a worker thread. The checklistRecyclerAdapter subscribes to the ChecklistCursor observable and is responsible for managing the cursor.
The code below seems to be the only way that I am able to get query.run() to run on a worker thread and the subscription to return on the main thread.
I guess it works, but I don't believe this would be the standard way to do this. Could anyone offer a better method than using observeOn twice?
compositeSubscription.add(db.createQuery(Checklist.TABLE, Checklist.QUERY_ALL)
.observeOn(Schedulers.newThread())
.map(query -> new ChecklistCursor(query.run()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(checklistListRecyclerAdapter));
I would replace the first observeOn call with subscribeOn(Schedulers.io()), in order to perform the query operation in a background thread. Map operator will still perform the operation in the background thread, and after that, you can change with observeOn to update your adapter. I think this is the only way to do this.
Related
According to Docs:
Room doesn't support database access on the main thread unless you've called allowMainThreadQueries() on the builder because it might lock the UI for a long period of time. Asynchronous queries—queries that return instances of LiveData or Flowable—are exempt from this rule because they asynchronously run the query on a background thread when needed.
I want to know how the LiveData observable do works in the background and get wrapped objects asynchronously?
LiveData it is all about main thread (ui), when you are creating your dao class, some thing like this:
#Dao
public interface DaoExample {
#Query("select * from example")
LiveData<List<ExampleModel>> getAllModels();
}
Under the hood room creates all needed stuff, some thread for background processing, live data for posting the latest data from table and so on. All this logic encapsulated inside dao/database. When you will insert a new row, room will save it (worker thread) then notify all observables stream (ui thread).
Let's say your DAO has this method that updates user records in the DB:
#Update
fun update(user: User): Single<Int>
Recently I started learning RxJava and so far I have seen lots examples like following:
// Example 1
disposable.add(dao.updateUser(user)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
Log.d(TAG, "response received")
}
In the above example, I understand that as soon as the subscription starts, updateUser() will be executed on a worker thread and the subscriber will be notified and run in the main thread once the execution completes.
But what if you are not interested in the result of updateUser(), and all you want is just to execute updateUser() on a worker thread?
So far I have tried doing:
// Example 2
dao.updateUser(user)
or
// Example 3
dao.updateUser(user).subscribeOn(Schedulers.io())
But they didn't work. It seems the update requests are never executed, nothing was logged and records didn't change. I am guessing that's because there isn't any subscriber attached to it.
For now I am forcing it to work by attaching a random subscriber that doesn't really do anything like the one in Example 1. One of the problems with the approach is that I might need to make this request a lot and that might create a lot of dummy subscribers, not to mention that the code looks really bad.
Could you help me find a better way of handling this?
But You already wrote answer for Your question.
You can just call:
dao.updateUser(user).subscribe()
If You want manipulate / jump between thread you are doing something like in Example 1.
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.
I'm currently implementing Room to replace my old SQL code, but I'm running into an issue where my query is very slow when running in the background.
For example, I have two identical queries, one that runs on the UI thread, and another that returns a Single. I'm using allowMainThreadQueries() to test this case.
#Query("SELECT * FROM event ORDER BY `begin` ASC LIMIT $LIMIT")
fun getUIThreadSchedule(): List<Event>
#Query("SELECT * FROM event ORDER BY `begin` ASC LIMIT $LIMIT")
fun getSchedule(): Single<List<Event>>
Now, when I run both of these and compare the time to give me a result, they are very different.
This will take ~6ms to complete.
val events = database.getUIThreadSchedule()
And this will take ~360ms to complete.
database.getSchedule()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// elements are now here
}, {
// show an error view
})
I tried using other options, such as Flowable, but the result is the same.
Does anyone know what I could be doing wrong?
Thanks.
After a lot of looking into the issue, I found the why this call was taking longer than the blocking call.
When calling getSchedule(), the subscribe block is not run right when the query is completed. It has to wait for the UI thread, so if that's blocked in another aspect, it will have to wait.
// start query
database.getSchedule()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// once ui thread is available, display content
}, {
// ...
})
The reason my UI thread was blocked is because I'm testing on cold start, so my Fragment would created, my query would be fired off, but then it would have to wait for the first frame of the rest of the UI to render before I could handle the getSchedule() result.
With the blocking call, it already had the UI thread, so there was no waiting.
I have read that one of the ways to query data in room database is to use Livedata which can be used on the main thread as it is asynchronous.
I would like to use LiveData instead of RxJava or AsyncTask.
For this in my repository class I have function getSomeData() which returns LiveData> and I call this function in my viewModel constructor:
private var mObservableSomeData: LiveData<List<SomeData>>
init {
mObservableSomeData = repository.getSomeData()
}
fun getSomeData(): LiveData<List<SomeData>> {
return mObservableSomeData
}
However it crashes saying:
Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
What should I do?
As pointed out by #LieForBananas, most probably you are getting error while doing insertion. Whenever you have #Query and you are wrapping returned data into an observable e.g. LiveData or Flowable, your query gets executed on background thread by default.
Remember that Room Database ensure that the query returning observable is run on background thread. This is why, if you are wrapping returned value in Flowable, you don't have to explicitly write .subscribeOn(Schedulers.io) while creating observer. Whereas If you are using Flowable for network call(Single might be better because usually we need to emit only once), then you have to explicitly write .subscribeOn(Scheduler.io()) to run the call on a background thread.
Room doesn't allow database operation on the Main thread unless you allow database on the main thread with allowMainThreadQueries().
MyApp.database = Room.databaseBuilder(this,AppDatabase::class.java,"MyDatabase")
.allowMainThreadQueries()
.build()