I have Android Room's SQL query, that return flowable:
#Query("SELECT * FROM exercices WHERE lang = 'ru' AND id_exercice = :id")
Flowable<Exercices> getExercicesById(int id);
In my repository, I need get emitted element, than change his boolean value, then call new method, that must returns completable.
That's what I try:
#Override
public Completable setExerciseUsed(int id) {
return mDatabase.exerciseDao().getExercicesById(id)
.doOnNext(exercise -> exercise.setIs_used(1))
.flatMapCompletable(exercise ->
Completable.fromAction(() -> mFitnessDatabase.exerciseDao().addExercise(exercise)));
}
Also i tried:
Exercices e = mDatabase.exerciseDao().getExercicesById(id).blockingFirst();
e.setIs_used(0);
return Completable.fromAction(() -> mDatabase.exerciseDao().addExercise(e));
But t not works properly. It seems like flowable emits many elements, and it going to stuck in cycle, after subscription.
Since your DAO returns Flowable, it will emit fresh data each time table is modified.
So after calling mFitnessDatabase.exerciseDao().addExercise(exercise),
getExercicesById will emit new data, thus the chain will execute forever.
If you want Room not to emit data - just change Flowable to Single.
Since you expect the one value to e returned, it's a good idea to limit the result to one item: "SELECT * FROM exercices WHERE lang = 'ru' AND id_exercice = :id LIMIT 1".
But actually, in your case, if you want to change a parameter of an item, it's much more efficient to do this within one query.
It may look like:
#Query("UPDATE exercices SET is_used = 1 WHERE lang = 'ru' AND id_exercice = :id")
fun setIsUsed(id: Int)
I don't know much about flowables or how to unsubscribe from one source with those, but have you thought about using LiveData? You can simply add a source to a LiveData object, then remove the connection to the source, change the object and execute your method without going into loop.
Another idea would it be that you retrieve your object, keep the connection and updating the value outside of that. Because you are keeping the database connection with LiveData, the observer would execute the onChanged again (because the source = database has been changed). You only would need to make sure that this method returns for example null (via setValue) for the time being until the database uploaded the new object value.
If you want to stay with flowables, maybe you should consider adding a condition which will prevent the app from re-applying the integer to the object (and sending it to the database). Does that makes sense to you?
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.
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.
I've got a question about Flowables. I already have a few solutions for this issue, but I would like to double-check if these are the best possible solutions or not.
Context
I have an Interactor that is supposed to bookmark recipes on the DB. It looks like this:
/**
* This Interactor marks a recipe as "bookmarked" on the DB. The Interactor actually switches
* the isBookmarked value of the related recipeId. If it was marked as true, it switches its value
* to false. If it was false, then it switches its value to true.
*/
class BookmarkRecipeInteractorImpl(
private val recipesCacheRepository: RecipesCacheRepository
) : BookmarkRecipeInteractor {
override fun execute(recipeId: Int, callback: BookmarkRecipeInteractor.Callback) {
// Fetches the recipe from DB. The getRecipeById(recipeId) function returns a Flowable.
// Internally, within the RecipesCacheRepository, I'm using room.
recipesCacheRepository.getRecipeById(recipeId).flatMap { originalRecipe ->
// Switches the isBookmarked value
val updatedRecipe = originalRecipe.copy(
isBookmarked = !originalRecipe.isBookmarked
)
// Update the DB
recipesCacheRepository.updateRecipe(updatedRecipe)
// Here's the issue, since I'm updating a DB record and the getRecipeById returns
// a Flowable, as soon as I update the DB, the getRecipeById is going to get triggered
// again, and switch the value again, and again, and again...
}
.subscribe(
{
callback.onSuccessfullyBookmarkedRecipe(it.response)
},
{
callback.onErrorFetchingRecipes()
}
)
}
}
So, if you follow the code, the error is pretty straightforward. I get stuck on a loop, where I constantly change the recipe record.
Possible solutions
1) Have two different functions on my DAO, one called getRecipeByIdFlowable(id) that returns a Flowable, and another called getRecipeByIdSingle(id) that returns a rx.Single. That way I can expose the getRecipeByIdSingle(id) through the Repository and use it instead of the function that returns the Flowable. That way I cut the loop.
Pro: It works.
Con: I don't like having functions like this on my DAO.
2) Save the Disposable on a lateinit property and dispose it as soon as the subscriber triggers the onNext().
Pro: It works.
Con: I don't like having to do something like this, feels hacky.
3) Using ...getRecipeById(recipeId).take(1).flatMap... so it only handles the first emitted object.
Pro: It works, it looks tidy.
Con: I'm not sure if there's a better way to do it.
Question
Ideally, I would like to call some function that just allows me to disable the Flowable behavior and prevent it from emitting more items if the DB changes. So far the solution that I like the most is #3, but I'm not really sure if this is the right way to do it.
Thanks!
Edit 1
I'm just adding a bit more of information about the use case here. I need an Interactor that given a recipeId changes the isBookmarked value on DB to its oposite.
The DB records look like:
data class DbRecipeDto(
#PrimaryKey
val id: Int,
val name: String,
val ingredients: List<String>,
val isBookmarked: Boolean = false
)
I know that maybe there's some other ways in which I could tackle this issue differently. Maybe I could pass the recipeId arg and a bookmark (Boolean) argument and just run the update query.
But this use case it is totally made up, just an example; The thing that I'm trying to figure out how to prevent a Flowable from emitting more items if something changes on the DB.
You should probably call .take(1).singleOrError() on the end of getRecipeById(recipeId).
This will take the first item (or the error) emitted by the Flowable retrieved by calling getRecipeById and wrap it in a Single. In my opinion this correctly matches the semantics of what you want to achieve.
In addition, if I recall correctly, because you will be subscribing on a Single by doing this, your Flowable will not continue to do work after the first item is consumed by the downstream call to singleOrError.
Note: Room checks invalidations based on table modifications, which means it may dispatch false positive notifications. Guide to app architecture
What is an invalidation for Room? How does it cause false positive notifications?
That means,
Suppose you have below query
#Query(“SELECT * FROM Users WHERE userId = :id)
fun getUser(id: String): LiveData<User>
And you are observing it like
getUser("id_1").observe(this, Observer{
// do something
})
There is nothing wrong in above method, but there is the case of false positive notifications.
Now suppose that from somewhere else you have deleted user with an userId = "id_2". At this point, you know that you don't need to get notified for your earlier getUser("id_1") call as it has nothing to do with your operations on id_2. But you still will get notified and your // do something will run again. That's because, Room will know that something has been changed, but doesn't know what has been changed and hence it will just re-query and send the result back again.
In order to bypass this false positive notification, you can use MediatorLiveData when you have LiveData as return type or distinctUntilChanged if you are have RxJava as return type in your Daos.
Ref: 7 Pro-tips for Room
How do I make a single row query with Android Room with RxJava? I am able to query for List of items, no issues. Here, I want to find if a specific row exists. According to the docs, looks like I can return Single and check for EmptyResultSetException exception if no row exists.
I can have something like:
#Query("SELECT * FROM Users WHERE userId = :id LIMIT 1")
Single<User> findByUserId(String userId);
How do I use this call? Looks like there is some onError / onSuccess but cannot find those methods on Single<>.
usersDao.findByUserId("xxx").???
Any working example will be great!
According to the docs, looks like I can return Single and check for EmptyResultSetException exception if no row exists.
Or, just return User, if you are handling your background threading by some other means.
#Query("SELECT * FROM Users WHERE userId = :id")
User findByUserId(String id);
How do I use this call?
usersDao.findByUserId("xxx")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(user -> { ... }, error -> { ... });
Here, I show subscribe() taking two lambda expressions, for the User and the error. You could use two Consumer objects instead. I also assume that you have rxandroid as a dependency, for AndroidSchedulers.mainThread(), and that you want the User delivered to you on that thread.
IOW, you use this the same way as you use any other Single from RxJava. The details will vary based on your needs.
According to the Google docs: For single result queries, the return type can be any data object (also known as POJOs). For queries that return multiple values, you can use java.util.List or Array.
Google docs