I want a way to search and update an existing entry in Room with RxJava. If there's no record it should create a new one.
For example, lets say I have the following queries:
#Insert
Single<Long> createContent(Content content);
#Query("SELECT * FROM Content WHERE contentId = :contentId")
Single<Content> searchContent(String contentId);
My Goals:
Check if there's a previous data and return its value
If there's no record create a new one and return it's value
Problem with this approach:
Whenever there's no record the from #Query, the Single<Content> directly goes to error ignoring any map/flatMap operator
The #Insert query returns a Single<Long> but the #Query returns a Single<Content>
Is there any way to call and return a new Observable from the error? Something like this:
daoAccess.searchContent(contentId)
.subscribeOn(Schedulers.io())
.map(Resource::success)
.onErrorResumeNext(new Function<Throwable, Single<Content>>() {
#Override
public Single<Content> apply(Throwable throwable) throws Exception {
return daoAccess.createContent(contentId);
}
})
You could use Single.onErrorResumeNext():
daoAccess.searchContent(contentId)
.subscribeOn(Schedulers.io())
.map(Resource::success)
.onErrorResumeNext(throwable ->
daoAccess.createContent(content)
.map(id -> Resource.success(content))
)
Related
I'm having a weird problem with my repository implementation. Every time I call my function that's supposed to get data from the database and update the database with a network call, I receive multiple results from my database observer.
override fun getApplianceControls(
serialNumber: SerialNumber
): Flowable<ApplianceControlState> {
val subject = BehaviorProcessor.create<ApplianceControlState>()
controlsDao.get(serialNumber.serial)
.map { controls ->
ApplianceControlState.Loaded(controls.toDomainModel())
}
.subscribe(subject)
controlApi.getApplianceControls(serialNumber.serial)
.flatMapObservable<ApplianceControlState> { response ->
val entities = response.toEntity(serialNumber)
// Store the fetched controls on the database.
controlsDao.insert(entities).andThen(
// Return an empty observable because the db will take care of emitting latest values.
Observable.create { }
)
}
.onErrorResumeNext { error: Throwable ->
Observable.create { emitter -> emitter.onNext(ApplianceControlState.Error(error)) }
}
.subscribeOn(backgroundScheduler)
.subscribe()
return subject.distinctUntilChanged()
}
#Dao
interface ApplianceControlsDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(controls: List<TemperatureControlEntity>): Completable
#Query("SELECT * FROM control_temperature WHERE serial = :serial")
fun get(serial: String): Flowable<List<TemperatureControlEntity>>
}
Basically, if I call getApplianceControls once, I get desired result. Then I call again, with another serial number, which is empty and I get the empty array. But then I call a third time, but with the same serial number as the first time and I get a mix of correct results and empty array after the insert call is made.
Like this:
1st call, to serial number "123" -> Loaded([control1, control2, control3])
2nd call, to serial number "000" -> Loaded([])
3rd call, to serial number "123" -> Loaded([control1, control2, control3]), Loaded([]), Loaded([control1, control2, control3])
If I remove the db insert from the api response, it works fine. Everything weird occurs after insert is called.
Edit: getApplianceControls() is called from the ViewModel.
fun loadApplianceControls(serialNumber: SerialNumber) {
Log.i("Loading appliance controls")
applianceControlRepository.getApplianceControls(serialNumber)
.subscribeOn(backgroundScheduler)
.observeOn(mainScheduler)
.subscribeBy(
onError = { error ->
Log.e("Error $error")
},
onNext = { controlState ->
_controlsLiveData.value = controlState
}
).addTo(disposeBag)
}
As i mention in comment you have 2 subscriptions that are not unsubscribed anywhere, it could cause memory leak (it doesn't dispose when subject is disposed), also with such implementation you ignore API errors.
i'd try to change it to:
override fun getApplianceControls(serialNumber: SerialNumber): Flowable<ApplianceControlState> {
val dbObservable = controlsDao.get(serialNumber.serial)
.map { controls ->
ApplianceControlState.Loaded(controls.toDomainModel())
}
val apiObservable = controlApi.getApplianceControls(serialNumber.serial)
.map { response ->
val entities = response.toEntity(serialNumber)
// Store the fetched controls on the database.
controlsDao.insert(entities).andThen( Unit )
}
.toObservable()
.startWith(Unit)
return Observables.combineLatest(dbObservable, apiObservable) { dbData, _ -> dbData }
// apiObservable emits are ignored, but it will by subscribed with dbObservable and Errors are not ignored
.onErrorResumeNext { error: Throwable ->
Observable.create { emitter -> emitter.onNext(ApplianceControlState.Error(error)) }
}
.subscribeOn(backgroundScheduler)
//observeOn main Thread
.distinctUntilChanged()
}
I'm not sure if it solves the original issue. But if so - the issue is in flatMapObservable
ALSO would be useful to see controlApi.getApplianceControls() implementation.
I'm studying Rxjava2 and I'm trying to integrate the Room Library with Rxjava2. The problem is: I have a populated table and every time I login in the app, I need to delete this table and then insert a new content in database. Separately, the delete and insert works fine, but when I try to insert new values after I delete the table content, the delete method deletes all the new values.. (some parts of the code is in kotlin and others in java)
I already tried this: RxJava2 + Room: data is not being inserted in DB after clearAllTables() call, but no success..
DAO
#Dao
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(list:List<Something>)
#Query("DELETE FROM SomethingTable")
fun delete()
#Query("SELECT * FROM SomethingTable")
fun getAll(): Flowable<List<Something>>
My class that calls the DAO (CallDao)
//insert
fun insertInDB(list: List<Something>) {
Completable.fromAction {
dbDAO!!.insert(list)
}.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe()
}
//delete
fun clean() {
Completable.fromAction {
dbDAO!!.delete()
}.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.single())
.subscribe()
}
//search all
fun findAll(): Observable<List<Something>>? {
return Observable.create { subscriber ->
dbDAO!!.getAll()
.subscribeOn(Schedulers.io())
.subscribe {it->
subscriber.onNext(it)
}
}
}
Method that is called when I click in login button
private void clearAndInsertInDB() {
CallDao callDao= new CallDao(getActivity());
//delete all table values
callDao.clean();
Something sm = new Something("test1", "test2");
ArrayList<Something> list = new ArrayList<>();
list.add(sm);
list.add(sm);
//insert new values
callDao.insertInDB(list);
//get all new values in DB
callDao.findAll()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(res -> {
//here gives me a IndexOutOfBoundsException
Log.d("logDebug", res.get(0).getCodeExemple());
});
}
Any corrections in my code is also welcome :) , but the main problem is that the delete method deletes all the new insert values and it should delete only the old values.
You are making two asynchronous calls: one to delete the users and another to insert them again. However, even though you call first the callDao.clean(); method and after that you call callDao.insertInDB(list); , it is not guaranteed that the clean() operation will finish before the insertInDB() operation (because that's how asynchronous calls work).
This is what is happening:
Instead, you should chain your async calls , in such a way that the second one gets called as soon as you know that the first one has already finished.
How to achieve that using RxJava and Completable? Using the andThen operator as stated in this answer
You should modify your clean() and insertInDB() methods to return the Completables, use andThen to chain them, and then subscribe.
Simple example using RxJava and andThen()
FakeDatabase db = Room.databaseBuilder(this, FakeDatabase.class, "fake.db")
.fallbackToDestructiveMigration()
.build();
UserDao userDao = db.userDao();
User user1 = new User("Diego", "Garcia Lozano", "diegogarcialozano#fake.com");
User user2 = new User("Juan", "Perez", "juanperez#fake.com");
User user3 = new User("Pedro", "Lopez", "pedrolopez#fake.com");
List<User> users = new ArrayList<>();
users.add(user1);
users.add(user2);
users.add(user3);
Completable deleteAllCompletable = Completable.fromAction(userDao::deleteAll);
Completable insertUserCompletable = Completable.fromAction(() -> userDao.insertAll(users));
deleteAllCompletable
.andThen(Completable.fromAction(() -> System.out.println("Delete finished")))
.andThen(insertUserCompletable)
.andThen(Completable.fromAction(() -> System.out.println("Insert finished")))
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.single())
.subscribe();
Checking the Logcat after execution, you can see that the operations were executed in the proper order:
2018-11-19 16:07:02.056 10029-10047/? I/System.out: Delete finished
2018-11-19 16:07:02.060 10029-10047/? I/System.out: Insert finished
Afterwards, I checked the content of the database using the tool SQLite Browser and saw that the insert worked properly.
Using #Transaction in the DAO
You can get a better solution for your problem without using RxJava at all. Instead, you can define a Transaction in your DAO using the #Transaction annotation, as explained in this post. It would look something like this:
Dao
#Dao
public abstract class UserDao {
#Transaction
public void deleteAndCreate(List<User> users) {
deleteAll();
insertAll(users);
}
#Query("DELETE FROM User")
public abstract void deleteAll();
#Insert
public abstract void insertAll(List<User> users);
}
Activity
Completable.fromAction(() -> userDao.deleteAndCreate(users))
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.single())
.subscribe();
Checking the table
Personally, I would do it with the #Transaction annotation.
I need to check if database is empty and if it is, then download data with retrofit2, select what I need and insert it to database and finally return inserted data from database. I have tried to do it with this example https://stackoverflow.com/a/48478847/5184417, but I can't figure out the part with inserting data to database. It gives an error about return of flatmap that insert to database doesn't return anything.
I have this piece of code
fun getEmployees(): Flowable<List<Employee>> {
return employeeDao.getEmployeeCount()
.take(1)
.flatMap { counts ->
if (counts.isEmpty() || counts[0] == 0) {
Api.getAPIService().getDepartments()
.flatMap{ response ->
employeeDao.deleteAll()
for (departments in response.Departments) {
if (departments.Name == "AR") {
for (employee in departments.employees) {
employeeDao.insert(employee)
}
}
}
state.postValue(RepositoryState.READY)
}
.ignoreElements()
.andThen(employeeDao.getAll())
}
employeeDao.getAll()
}
}
interface ApiService {
#GET("departments")
fun getDepartments() : Single<Departments>
}
#Dao
interface EmployeeDao {
#Query("SELECT * FROM employees")
fun getAll(): Flowable<List<Employee>>
#Query("SELECT count(1) FROM employees")
fun getEmployeeCount(): Flowable<List<Int>>
#Insert(onConflict = REPLACE)
fun insert(employee: Employee)
}
Thanks for any help!
flatmap is used to chain the observable. It's syntax is:
observable1
.flatmap(i-> {
return observable2;}
)
So the point is that you should return an Observable inside flatmap and that Observable will be propagated down(I mean observable 2 in above code). One possible solution is that make employeeDao.getAll() to return a flowable OR some how just wrap the output of employeeDao.getAll() inside Observable.just() or Observable.create() or whatever method you know.
EDIT: You must return an observable inside flatmap, you are not using any return statement.
I need proper approach to insert object to room and get rowId via rxjava2 way. For example: i have a Body entity
long insertBody(Body body);
In viewmodel
Body body = new Body(contactId, msgText);
getCompositeDisposable().add(
Single.fromCallable(() -> getDataSource().insertBody(body))
.subscribeOn(getSchedulerProvider().io())
.observeOn(getSchedulerProvider().ui())
.subscribe(bodyId -> onContinueNewMessage(msgId, conversationId, bodyId,
forwardBodyId, replyMsgId, createdTimestamp), Timber::e)
);
The question is what if i write body object and get its rowId in multiple places? I need a method which insert body via rxjava way and return like Single... Thanks in advance!
You could consolidate the code you have inside the add() into methods.
For example:
public class Example {
public static Single<Integer> insertBodySingle(Body body, DataSource dataSource) {
return Single.fromCallable(() -> dataSource.insertBody(body));
}
public static Single<Integer> insertBodySingleThreaded(Body body, DataSource dataSource) {
return insertBodySingle(body, dataSource)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
Then you can use these like this:
Example.insertBodySingle(body, dataSource).subscribe(bodyId -> )
I'm quite new to Room and RXJava and I want to use them to perform a quite simple query but I have problem implementing the RX part and handle results.
#Dao
interface DepartmentDao{
//....
#Query ("SELECT employeesIds FROM Department WHERE Department_name LIKE :name")
fun getEmployeesIds(name:String):String //this is a jsonArray stored as string
}
Then I have Kotlin object where I write some other methods related to the database others than ones from #Dao
object DBManager {
fun getEmployeesIdsJsonArray():Completable = Completable.fromCallable {
mDataBase.DepartmentDao().getEmployeesIds(deptName)
}
}
I want to query this in my Fragment and use the query result (a string in this case) when the query completes. This is where I get locked and need your help.
DBManager.getEmployeesIdsJsonArray()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( //here I get locked, how can I handle this?)
I expect to have something like
{
onSuccess -> jsonString , //this is the string resulted, feel free to use it
onError -> Log.e(TAG, "query failed")
}
but I'm not able to implement it successfully without all kind of errors regarding type expectations.
Well. Completable returns nothing, just termination event onComplete/onError
Try :
Return Single in your Dao
Your subscribe method should looks like subscribe({function1},{function2})
And never use Schedulers.newThread() for IO operations. Instead this prefer Schedulers.io(), because it use reusable threads from thread pool, while Schedulers.newThread() create just a new thread, which is not reusable
I think the syntax you're looking for is this:
DBManager.getEmployeesIdsJsonArray()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( { jsonString ->
// onNext
// Do something with jsonString
}, { throwable ->
// onError
// Do somethign with throwable
} )