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.
Related
In room, I have a dao to something like this:
#Dao
interface FacultyDao {
#Query("select * from faculty")
fun getAll(): LiveData<List<Faculty>>
...
}
And inside the repository, I'm simply calling this method and logging it:
class FacultyRepository(application: Application) {
private val facultyDao: FacultyDao
init {
val db: AppDB = AppDB.getInstance(application)
facultyDao = db.facultyDao()
}
fun getAllFaculty(): LiveData<List<Faculty>> {
val v = facultyDao.getAll()
Log.d("muaxx", v.value.toString())
return v
}
...
}
But the thing is it's returning me null, but when I ran that query in inspector it worked. Am I missing something?
LiveData doesn’t immediately have an initial value. Room queries the database and gets the result on a background thread. Then on the next loop of the main thread, the LiveData’s value will be set to this retrieved value. You are logging value too early. The initial value is going to appear some time in the future, after this function has already returned.
Normally you should only be getting a LiveData value through observing it.
Directly checking the value should usually only be done when you are managing a MutableLiveData and are using the previous value to help determine the next value that you are going to post.
Live data gives us real-time data. Therefore, for the first time, you still don't have some in yourself. And it is waiting for the response of the database. If you want to see some of the live data, you must observe it so that after receiving the information, the observer will be called and the information will be logged.
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.
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 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()
I am trying to use RoomDatabase in my Android App. And I am using LiveData to be able to refresh my changes automatically inside my fragment.
The first time I am running my app I am getting the data from the API, creating my RoomDatabase and storing my data.
The second time I run my app I want to check if my DataBase is not empty. But while using LiveData: the following code is returning null.
AppDatabase.getInstance(getContext()).getRecipeDao().getAllRecipes().getValue();
I have read that "if the response is an observable data type, such as Flowable or LiveData, Room watches all tables referenced in the query for invalidation".
How to check if my RoomDatabase has data or is empty?
So after implementing myself I found that you need to do a few things:
Make sure you have an Observer for changes to the LiveData
You need to call observeForever(Observer<T> observer) unless you are using a LiveCyclerOwner then use that instead with: observe (LifecycleOwner owner, Observer<T> observer)
Finally, there is an interesting note on getValue():
Returns the current value. Note that calling this method on a
background thread does not guarantee that the latest value set will be
received
So to reiterate, I think your approach does not work.
You will need to create some type of separate check rather than use a method that returns a LiveData class as noted since it does not guarantee the latest value set is received by calling getValue().
I would recommend something super simple in the end such as adding a new method to your Dao
#Query("SELECT * FROM recipes LIMIT 1")
Recipe getAnyRecipe();
and do this check looking for null to see if anything exists in the recipes table.