I'm using Room to handle my local database, and LiveData to handle the DAOs.
So I'm using LiveData as thread handler for my transactions.The question is how can I do insert and updates with LiveData? Or generally how can void functions return LiveData in the Room?
#Query("select * from table")
fun getAll(): LiveData<List<T>>
#Insert
fun insert(T data): LiveData<?> // What should be the generic, since it's void?
In RxJava we have something like this:
#Insert
fun insert(T data);
The question is how can I do insert and updates with LiveData? Or generally how can void functions return LiveData in the Room?
You can't use LiveData object itself to insert or update data in your database (db). LiveData is a wrapper around the object loaded from db which can be observed.
In order to perform insert/update operations you need to pass the object annotated with #Entityannotation to your DAO method parameter. You can do it either by calling LiveData#getValue(), or observe the LiveData in Activity, or Fragment and update a local variable via the observer and pass said variable.
Related
I'm developing MVVM kotlin application with repository pattern
I have separated my model classes as below
classes represents the data coming from network
classes represents entities for the room database
classes that represents the domain
In my repository I want always to return the domain object not the network object or the entity object.
In my Dao I insert and retrieve entity object as below
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAsteroids(asteroid: List<AsteroidEntity>)
#Query("SELECT * FROM asteroid_tbl")
fun getAsteroidsFromDb () : LiveData<List<AsteroidEntity>>
And I have a function in the repository that returns the list of asteroids as Live data from room as below
override suspend fun getAsteroids(): LiveData<List<Asteroid>> {
return appLocalDb.asteroidDao.getAsteroidsFromDb()
}
But the issue is the dao returns a LiveData<List<AsteroidEntity>>
while the repository I want it to return the domain model as
LiveData<List<Asteroid>>
Any suggestion on how to solve this problem?
Use Transformations.map
override suspend fun getAsteroids(): LiveData<List<Asteroid>> {
return Transformations.map(appLocalDb.asteroidDao.getAsteroidsFromDb()) { it.map{ item -> Asteroid(item.name,....)} }
}
For Further Documentation checkout the docs
https://developer.android.com/reference/android/arch/lifecycle/Transformations
When using Livedata as a return type for a select* query on a table in Room, then I observe on it, I get triggers if I update/insert/delete an entry in that table. However, when I tried using Kotlin Flow, I only get 2 triggers.
The first trigger gives a null value as the initial value of the stateflow is a null. The second trigger is the list of entries in the Room table.
If I perform an insert/delete action on the DB, I receive a trigger from the StateFlow.
However, If I update an entry, the Stateflow doesn't trigger.
N.B: The update operation works correctly on the DB. I checked using DB inspector.
Data class & DAO
#Entity
data class CartItem (
#PrimaryKey
val itemId: Int,
var itemQuantity: Int=1
)
#Dao
interface CartDao {
#Query("SELECT * FROM CartItem")
fun getAllItems(): Flow<List<CartItem>>
#Update
suspend fun changeQuantityInCart(cartItem:CartItem)
#Insert
suspend fun insert(item: CartItem)
#Delete
suspend fun delete(cartItem:CartItem)
}
ViewModel
val cartItems: StateFlow<List<CartItem>?> =
repo.fetchCartItems().stateIn(viewModelScope, SharingStarted.Lazily, null)
Fragment
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.cartItems.collect {
Log.e("Update","Update")
}
My pitfall was that I was updating the object like this:
currentItem.itemQuantity = currentItem.itemQuantity + 1
changeQuantity(currentItem)
(currentItem is an object of class CartItem which is received initially from the getAllItems Flow in the DAO.)
(changeQuantity fun calls the changeQuantityInCart fun in the DAO.
This caused the reference of the CartItem object in the StateFlow to hold the updated value of the object with the new itemQuantity value before calling the update on the DB.
After that, when calling the Update fun in the DAO, the DB entry is updated and the Flow value changes, but when putting it in the Stateflow no changes are detected. Thus, the stateflow doesn't trigger as it is how stateflows differ from livedata.
In the case of livedata, it will trigger regardless if the new value is the same or not.
Thus, to solve this bug do not change the value of the object in the stateFlow before calling a DB update operation like this:
val updatedCartItem = cartItem.copy(itemQuantity = cartItem.itemQuantity + 1)
changeQuantity(updatedCartItem)
I'm posting my room query result using the below code
_data.postValue(databaseImpl.findRepoById(id).value)
But it seems the returned object is empty. I'm guessing we need to observe databaseImpl.findRepoById(id), not access it directly but since it's in a viewmodel I don't have a lifecycleOwner to assign to the room query livedata.
databaseImpl.observe(this, Observer { //!!this is modelview and not activity
_data.postValue()
})
What is the right way to use Room query and update a mediator live data?
You could try use coroutines to query your data so you can have something like this for your DAO:
#Query("SELECT * FROM users")
fun findRepoById(id): Flow<User>
In your DatabaseImpl class return have it as:
fun findRepoById(id): Flow<User> = userDao.findRepoById(id)
Then in your ViewModel you could do this:
viewModelScope.launch {
databaseImpl.findRepoById(id).map { _data.postValue(it) }
}
Have a look at this article Room and Coroutines by Florina Muntenescu
on how you could set up coroutines and use it with room.
I am using live data with room database and my activity observes live data provided from room database.
#Query("SELECT * FROM BUS WHERE BUS_CATEGORY = :busCategory")
LiveData<List<Bus>> getLiveBuses( String busCategory);
ViewModels gets LiveData via Dao(Data Access Object) and activity observes this live data.
Now it works fine. But when busCategory changes i can't modify this live data to get buses for newly selected busCategory.
So how can i observe this same liveData where query parameters is changeable?
I suggest you to to use viewModel. I did the query and observe changes using MutableLiveData.
First step
val mutableBusCategory: MutableLiveData<String> = MutableLiveData()
Setter for mutablelivedata
fun searchByCategory(param: String) {
mutableBusCategory.value = param
}
observable to observe the change
val busObservable: LiveData<Bus> = Transformations.switchMap(mutableBusCategory) { param->
repository.getLiveBuses(param)
}
and final step to observe the live data
busObservable.observe(this, Observer {
//your logic for list})
and to trigger mutablelivedata
searchByCategory(//categoryName)
I don't think this is a reasonable expectation. It would make more sense to fire off a new query and subscribe to that.
I am using Component libraries in my android app. in some case it is needed to use Livedata and observe its data but sometimes I just want to get some ordinary list not Livedata , How can I do that? query DB in simple way
p.s : I use getValue() but it returns null
Use query like this in DAO:
#Query("SELECT * FROM TABLE_NAME")
fun getListOfData(): List<Data>?
this will provide you list of data from your table, just like the select query passed in #Query parameter.
Edit:
When calling from main thread, you can use handler to do your job in background like below:
//Method from where you want your data from Db.
fun getMyList() {
Thread {
(your db object).(your dao).getListOfData()
}.start()
}
or you can allow your db to execute on main thread when building your room db like below (Though i wouldn't recommend this) :
Room.databaseBuilder(
...
)
.allowMainThreadQueries()
.build()
You can simply write query in your Dao which has return type as List and call from your ViewModel where you need those data.
Example :
//YourDao
#Query("SELECT * FROM YourTable")
List<YourModel> getAllYourTableData();
//YourRepo
public static List<YourModel> getAllData(){
return getYourModelDao.getAllYourTableData();
}
//Your ViewModel
public void someFunctionWhereYouNeedNormalData(){
//assign to list
YourRepo.getAllData();
}
Assuming you have knowledge about repo pattern in android arch components.