In SQL Brite, there's a method mapToOneOrDefault. Is there a similar thing in Room?
Say for Model
#Entity(tableName = "users")
data class User(#PrimaryKey val name: String)
and Dao
#Dao
interface UserDao {
#Query("SELECT FROM users where name = :name")
fun getUserByName(name: String): Flowable<User>
}
Not the stream returns nothing for getUserByName("John") if there's no John in DataBase. Is there a way to get a default value, say User("")?
Not the stream returns nothing for getUserByName("John") if there's no
John in DataBase. Is there a way to get a default value, say User("")
There is no default mechanism.
You could change from Flowable<User> to Flowable<List<User>>. In case of no user you will get an empty list back. You can use a map to check and return a default value or filter+switchIfEmpty.
Or you could change from Flowable to Single. With Single, in case of no rows, matching your query, onError will be triggered. You can then implement onErrorReturn or onErrorResumeNext to return a default value
You can use Maybe instead of the Flowable in this case.
Maybe: Conceptually, it is a union of Single and Completable providing the means to capture an emission pattern where there could be 0 or 1 item or an error signaled by some reactive source.
You can then use the operator defaultIfEmpty to map to a new object if the query didn't return a value.
Reference
Related
I have a situation where I need some information about my results, in my code I have this data class/Room's entity.
#Entity(primaryKeys = ["searchId","page","pr_id"])
data class ProductResponse(
var searchId: Int,
val page: Int,
#Embedded(prefix = "pr_")
val products: ProductSearchFormatted
)
Where you can see in the Android Studios App Inspector:
The data is saved as spected in ROOM, when I try to load it from ROOM:
#Query(
"SELECT * FROM PagedSearchResponse WHERE searchId ==:input"
)
fun loadPagedSearchResponse(input: Int): PagingSource<Int, ProductResponse>
I just need the data in the same order that was previously saved, and got the data in different order (ordered by pr_id):
I found out that if I change the primaryKeys order, like
#Entity(primaryKeys = ["pr_id","searchId","page"])
data class ProductResponse(
var searchId: Int,
val page: Int,
#Embedded(prefix = "pr_")
val products: ProductSearchFormatted
)
Now the data's order from ROOM is correct.
Why does this happen? Does the primaryKeys order matter?
Changing primaryKeys order changes the order of the data saved in ROOM
NO it does not, the data is ALWAYS saved in the order in which it is inserted. What is changing in your case, is the ORDER in which the data is extracted. That is because you aren't saying in what ORDER you want the data to be extracted and are leaving that choice to the query planner.
Why is this happened?, does the primaryKeys order matter?.
Yes it can do, especially in the absence of other indexes. Certainly pr_id before search_id will make a difference as the order within the index will be different and that as the WHERE clause is on the search_id then it is likely that the primary key index will be used (as in both cases search_id is an initial column (see the links below))
The query planner is an AI that tries to pick the fastest and most efficient algorithm for each SQL statement.
see :-
https://www.sqlite.org/queryplanner.html
https://www.sqlite.org/optoverview.html
https://www.sqlite.org/queryplanner-ng.html
If you want data to be in ORDER then you should specify an ORDER clause (ORDER BY ....). That is the only way to guarantee an ORDER. Assuming an ORDER without an ORDER clause will very likely result in issues.
Saying that using pr_id prior to search_id makes the composite (multiplte column) index likely to be more beneficial as the pr_id (according to the data shown) is less repeated than the search_id.
The way I fix it, it was simpler than I expected.
In the entity, I added a field called "order", and in my Mediator (I'm using Paging 3) basically did this:
list.mapIndex { s,t ->
ProductResponse(
...
order = s
...
)
}
That way, I'm following the EXACT order from Backend without the need to modify any primaryKeys.
I have a program where data is stored in a local database using Room, and I receive data through RxJava2. In Provider, the database created a method that takes one parameter - an identifier, by which it sends a request and receives certain data that corresponds to the identifier. But I want to pass as a parameter not one identifier, but an array of identifiers and get an array as well, but I don't know how. I don’t want to implement it through for, because I think there is a better solution, but I couldn’t find it. My code is shown below.
...
userDatabase.userDao().getById(id)
.subscribeOn(Schedulers.io())
.observeOn(SchedulerProvider.ui())
.map {
InfoStruct(
it.Name.toString(),
it.Id.toString()
)
}
.subscribe(
{println("${it.userName} || ${it.userId}")},
{println("Error")}
)
...
I pass one value to the getById method parameter - an identifier, and send a request like this: select * where id =: id. I want to pass an array of identifiers as a parameter to get data for several users at once, but I don't want to change the structure of the request. How can this be done?
You could use a dao, e.g. getByManyIds(), that uses a WHERE clause that returns a list based upon a provided list.
The SELECT clause could be along the lines of
#Query("SELECT * FROM the_table WHERE id IN (:idlist)"
List<your_type> getByManyIds(appropriate_type idlist);
appropriate_type should be an array of the id's int[], long[], String[] etc as is suitable (Room will generate the appropriate comma separated list).
Assuming that your Dao method looks like this:
#Query("SELECT * FROM user WHERE id = :id"
Single<User> getById(String id);
You need to do is change it to this:
#Query("SELECT * FROM user WHERE id IN (:ids)"
Single<List<User>> getByIds(List<String> ids);
And then use it like that:
userDatabase.userDao().getByIds(listOfIds)
.subscribeOn(Schedulers.io())
.observeOn(SchedulerProvider.ui())
.map { users ->
users.map {
InfoStruct(
it.Name.toString(),
it.Id.toString()
)
}
}
....
I have a room database setup and I want to query that database N number of times and combine the results of each query into a live data array to display back to the user.
I'm pretty sure I want to be using MediatorLiveData but every example online has a predefined amount of live data sources it is combining.
I have the following setup:
petDao
#Query("SELECT * FROM pet_table WHERE name LIKE :petName")
fun getPetsByPetName(petName: String): LiveData<Pet>
petRepository
fun getPetsByPetName(petNames: List<String>): LiveData<List<Pet>> {
for (petName: String in petNames) {
val pets = petDao.getPetsByPetName(petName)
// Combine into one live list of pets
}
}
Have you tried this in your DAO?
#Query("SELECT * FROM pet_table WHERE name IN (:petNames)")
fun getPetsByPetName(petNames: List<String>): LiveData<List<Pet>>
It should work with a list of up to 999 arguments. (not sure if the parameter has to be an array, or if the list is fine)
As an extension over SQLite bind arguments, Room supports binding a
list of parameters to the query. At runtime, Room will build the
correct query to have matching number of bind arguments depending on
the number of items in the method parameter.
https://developer.android.com/reference/androidx/room/Query
To me it seems more appropriate for the example you've given.
I know that I can do that by using data class but I want to achieve Like this
// This is My Query
#Query("SELECT loan_amount,adv_interest_amount FROM new_pledge_receive WHERE (new_pledge_receive.bill_date BETWEEN :fromDate AND :toDate)")
fun getPledgeReceiveAmount(fromDate: Long,toDate: Long):LiveData<List<Pair<Double,Double>>> ```
Pair class has 2 properties: first and second. Try to set the name of the selected columns to fit these names.
Something like: SELECT my_custom_field as first, my_another_field as second FROM...
As they mention on the official website
For SELECT queries, Room will infer the result contents from the method's return type and generate the code that will automatically convert the query result into the method's return type. For single result queries, the return type can be any java object. For queries that return multiple values, you can use List or Array. In addition to these, any query may return Cursor or any query result can be wrapped in a LiveData.
I am using liveData in my application. I do a DB Query which returns a LiveData<PagedList<Contact>> of contacts stored in DB.
I want to modify this livedata before giving it to observers. Suppose there are ten contacts in the LiveData list, i want to do some comparison with another list and set which contacts are primary in the LiveData list.
After doing this i want to give it to observers .
e.g -
val allContacts: LiveData<PagedList<Contact>> = getFromDB()
val list: ArrayList<String>() = list of some primary contacts
traverse allContacts and list and set which values in allContacts match the values in list.
which ever values in allContacts match, their isPrimary property will be set to true.
Now after modifying allContacts, i want to submit it to observers like:
allContacts.observe(this, Observer(adapter::submitList))
I tried LiveData.transform, But not able to use it properly.Can anyone suggest me how to achieve it using transform method or some other way.
What you are looking for is a Transformation. Use Transformations.map() to create a new LiveData from a function that will be run everytime the first LiveData changes.
e.g.
val allContacts: LiveData<PagedList<Contact>> = getFromDB()
val contactViewModel = Transformations.map(allContacts, {
// Transform and create new list from old
})
Your problem is rooted in the fact that you wish to "intercept" the updates that will be posted to the LiveData object by the DB. Regardless if this is a good approach you can technically achieve it by wrapping the LiveData that is returned by the DB with your own subclass of LiveData.
In other words. UI --observes--> YourLiveData --observes--> DBLiveData
P.S. I think genrally speaking you could solve your problem by modifying your DB query, but that is just me assuming you already have "primary contacts" in some other table