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()
Related
Recently I saw this - Most data sources already provide main-safe APIs like the suspend method calls provided by Room or Retrofit. Your repository can take advantage of these APIs when they are available.
What does this mean? Is the dispatcher under the hood Dispatcher.IO for Retrofit and Room? Or do I need to mention that explicitly, while making the request? Thank you.
withContext(Dispatchers.IO) {
// Some retrofit call or room query
}
No you don't need to mention dispatchers for Retrofit and Room. For Room when you mark a dao function as suspend fun it is guaranteed that it will not block main thread.
You can read this article https://medium.com/androiddevelopers/room-coroutines-422b786dc4c5
from the article
Room calls the CoroutinesRoom.execute suspend function, which switches to a background dispatcher, depending on whether the database is opened and we are in a transaction or not.
No, you don't need to switch context when calling suspend functions of Retrofit and Room. I'm not sure if they use Dispatcher.IO under the hood, maybe they use their custom context composed of thread pools, but it is guaranteed to be called in background thread.
For example you can call suspend Dao functions in ViewModel class like the following:
viewModelScope.launch {
val user dao.getCurrentUser()
// Update UI using user
}
assuming getCurrentUser() is a suspend function:
suspend fun getCurrentUser(): User
Marking your Retrofit HTTP request methods and Room DAO query methods as suspend tells both respective libraries to do the asynchronous work for you, meaning that you don't have to explicitly change threads with Dispatchers.IO at all.
Furthermore, even when a Room DAO method isn't marked suspend, but it returns a value wrapped in an Observable such as Kotlin's Flow, or RxJava's Flowable, or Jetpack's LiveData, Room will then run those queries asynchronously for you as well. As per the documentation.
That being said, you should still launch coroutines in that case, whenever you call your async, non-blocking methods with lifecycleScope or viewModelScope depending on where you're calling them from (Activity/Fragment or ViewModel) to harness the full power of suspending functions. lifecycleScope and viewModelScope use Dispatchers.Main.immediate by default, as already stated, you will not need to change Dispatchers.
Recently I'm learning to use DAO. From what I understand, all the #Insert, #Update, or #Query are executed asynchronously. And from the documentary, #Insert can return a long value, which is the new rowId for the inserted item (or List<long> if multiple items). Assuming my DAO looks like this:
#Insert
long insertTransaction(Transaction transaction);
#Insert
List<Long> insertTransactions(List<Transaction> transactions);
When I use these methods in an activity or fragment, does it mean I get the long value after the async task is completed?
<!-- language: lang-none -->
// Do I get 0 if the insert is not complete
// or it will wait till the insert is complete and return long?
long id = viewModel.insertTransaction(transaction)
If it waits for the async task to finish, won't it block the main thread (especially when inserting large lists)? And if not, how do I check if the insert is finished?
From what I understand, all the #Insert, #Update, or #Query are executed asynchronously.
By default all the #Insert, #Update, or #Query are executed synchronously. Room warns you about that and you cannot do sync calls without explicit using method allowMainThreadQueries in RoomDatabase.Builder.
Of course, it's not recommended to use synchronous calls. To use async calls you have several options (look at official documentation):
Kotlin coroutines (suspend keyword)
RxJava (set return type to Single, Maybe, Completable)
Guava (set return type to ListenableFuture).
In addition you can move DB operations to background thread explicitly using Threads/ThreadPools and to manage asynchronous work by yourself (using callbacks, for example).
Using one of options above you'll be notified when async task is over (notification's method depends on framework you've chosen). Otherwise you make sync call and block UI thread.
in my Dao I've defined a Query like this to check whether the database is empty or not:
#Query("SELECT * from meal_table LIMIT 1")
suspend fun getAnyMeal(): LiveData<Array<Meal>>
Within my populateDatabse function I would like to check, whether any item is inside my database with something like this:
suspend fun populateDatabase(mealDao: MealDao) {
if ((mealDao.getAnyMeal()).size < 1)
...
}
Unforunately size doesnt work in this context unless I am doing something wrong.
If someone has a tipp on how to solve this I would apreciate it! Thank you!
Unforunately size doesnt work in this context
It is because getAnyMeal returns you LiveData which has no property named size. LiveData is an observable object. It means that LiveData object you get by calling this method will return to its observers (only the ones who "subscribed" to updates) an array of Meal objects when this array will be available.
First of all, when you are using LiveData with Room you sort of giving Room a signal that you are not requesting a response immediately. LiveData is used when you want to get updates in future that will happen on change of any object in DB. Also you may want to use LiveData when you want to execute the SELECT query asynchronously. It means you call getAnyMeal method and it does not block UI thread.
When you are using suspend keyword you can remove LiveData from return type. When suspend function is executed it will synchronously return you the result.
Solution
Update getAnyMeal() to the next form or create the new method as it is declared below:
#Query("SELECT * from meal_table LIMIT 1")
suspend fun getAnyMeal(): Array<Meal>
If you declare getAnyMeal method like this you will be able to call size property on the return type as it directly returns you an array.
On LiveData with Room:
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.
Observable queries with LiveData for more info.
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).
I'm converting our project to work with Room ORM. It works great when I need a LiveData object updated, and works great for AsyncTasks such as insert, delete, etc., where I do not need a callback. But I'm confused what to use when I need a one-time query that requires a callback. The options are to call AsyncTask to query using the DAO implementation, or LiveData with Observer, and after the first receive, unregister the observer.
I would recommend sticking with the LiveData, particularly if you are using a ViewModel provided by Room. The ArchitectureComponents library really does a great job when it comes to bundling all Room, LiveData and ViewModels together, so try at best to stick with the convention.
The reasons I recommend sticking with LiveData and ViewModels are
ViewModels are Lifecycle aware, meaning they respond appropriately
to Fragment/Activity state changes which would otherwise leave your
AsyncTask either retrieving data for a dead Activity or doing work
when the Activity is no longer there potentially leading to MemoryLeaks
It's best practice (at least for Architecture Components) for a View to observe data/changes to data. If you need just a single callback, unsubscribe after you have received the data. Or use an RxJava single if you are currently using RxJava
If you feel the need to really want to use AsyncTask, I would suggest use an AsyncTaskLoader. This is a more robust/lifecycle aware background thread operation that will cache your data (it is very similar to an AsyncTask so the implementation details won't be too foreign), so if you rotate your device, the data will be cached and is immediately available, and you won't have a memory leak. Also check this Video on loaders by the Android team.
But I advise using the LiveData w/ ViewModels.
I had same issue with Room Implementation. As for LiveData return by room we can observe this on main thread without performing database operation on main thread.
But for fetching Raw object without LiveData we can't do it directly, As database operations on Main thread are not allowed.
I have used LiveData for list of things which I what to keep updated as soon as Database gets Updated. But for some operations I need to fetch it only once and stop observing the database because I know it won't change.
In my case I have used RxJava to Observe the query result only 1 time
Creating Observable for getting User by id from Repository
Observable.create(object : ObservableOnSubscribe<User> {
override fun subscribe(e: ObservableEmitter<User>) {
val user= userRepo.getUserById(currentUserId)
e.onNext(user)
e.onComplete()
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(GetCurrentUserByIdSubscriber())
Repository
fun getUserById(id: Int): User= userDao.getUserById(id)
Dao
#Query("SELECT * FROM users WHERE id= :id")
fun getUserById(id: Int): User
You can see I am returning plain User object instead of LiveData.
In Activity where I want to observe this
private inner class GetCurrentUserByIdSubscriber : Observer<User> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: User) {
// update the view after getting the data from database
user_name.text = t.name
}
override fun onComplete() {}
override fun onError(e: Throwable) {}
}
So this is how I can perform one time database fetch on IO thread with RxJava and get callback as soon as object is fetched from the database.
You can call unsubscribe on Subscription return from subscribe() method after results are obtained.
There is simple solution in Transformations method distinctUntilChanged.expose new data only if data was changed.
in case Event behaviour use this:
https://stackoverflow.com/a/55212795/9381524