I'm using RxJava2 with Room SQLite. My DAO:
#Dao
public interface HeroDao {
#Insert
long create(Hero hero);
}
And this is how I use it with RxJava2:
Observable.just((int) heroDao.create(hero))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(id -> /** do stuff **/);
But when I run the app, the error I get in Logcat is Cannot access database on the main thread since it may potentially lock the UI for a long period of time.I've tried attaching .allowMainThreadQueries() to the database builder and the insert goes through so that confirms the correctness of my observable. It appears LiveData and AsyncTasks are other approaches that I can try, but I'd rather not go there when I've already made serious investments in RxJava2.
So why am I getting that error? Isn't that subscribeOn(Schedulers.io()) sufficient to offload work off the main thread? Otherwise how do I accomplish that? It appears there are some subtleties about converting Room queries into observables that I'm missing?
It's because you used Observable.just. This method just wrap the object into Observable, so it's created before actual subscription and call heroDao.create(hero) on main thread. You should use Observable.fromCallable(), or maybe prefer Single.fromCallable.
Also you can define method in DAO like #Insert Single<Int> create(Hero hero);
There are some links that maybe be helpful:
Doing queries in Room with RxJava
7 Pro-tips for Room
the answer of #Dmitry Ikryanov is correct, but
you can also use defer()
createQuery().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intger -> {
//do sth with intege
Thread.currentThread().getName());
},
Throwable::printStackTrace
);
}
public Observable<Integer> createQuery() {
return Observable.defer(() -> {
try {
return Observable.just((Integer) heroDao.create(hero));
} catch (Exception e) {
return Observable.error(e);
}
});
}
Related
I'm trying to implement the same behavior of how Flow with Room Database in which it already listens to changes. I am pretty confused about how to use the RealmInstance.toflow() in which it returns a Flow<Realm>, however, I don't want that. If we compare it to Room Database, you can already specify what return type you it to be(ex. Flow<Entity>. Since Realm doesn't have any kind of DAOs, I am currently left with using
RealmInstance.addChangeListener{
realm->
//Handle DB Changes here
}
I don't know how to integrate the code above in my repository since you cant emit inside the addChangeListener because it needs a coroutine however,i don't want a solution of having to create a Global coroutine. I currently have this on my ItemRepository:
override suspend fun getItems(): Flow<Resource<List<Item>>> = flow{
RealmInstance.addChangeListener{
realm->
//Handle DB Changes here
//You cant emit() here since it needs a coroutine
}
}
The bottom line problem is: that I want to listen to changes in the realm in which my repository already returns Flow of the Object I want. Something like how Room DB and Flows work.
When you want to convert a callback listener like Realm changeListener to a Coroutine, you have two options :
One- Using CallBackFlow :
return callbackFlow<Location> {
RealmInstance.addChangeListener{
realm->
trySend(realm.toString()) // for example to return a string
}
addOnFailureListener { e ->
close(e)
}
awaitClose {
// remove and close your resources
}
}
TWO- Use suspendCancellableCoroutine
return suspendCancellableCoroutine { continuation ->
RealmInstance.addChangeListener{
realm->
//Handle DB Changes here
continuation.resume(returnValue)
}
continuation.invokeOnCancellation {
// do cleanup
}
I want to implement method to edit a note, save it to local database (cache) and then send it to the server as a POST request. I am learning RxJava and I wanted to create Observable from the note and then apply transformations on it, like to map it to an Entity model and saving. The issue that my method returns Completable and this chain returns Observable<Completable>. How to unwrap the Completable from this Observable which I used only to start RxJava stuff. Each editNote() methods returns a Completable.
override fun editNote(note: Note): Completable {
return Observable.just(note)
.map { mapper.mapToEntity(it) }
.map { noteEntity ->
factory.getCacheDataStore().editNote(noteEntity)
.andThen { factory.getRemoteDataStore().editNote(noteEntity) }
}
}
=======================================================
UPDATE
Finally, I managed to find "a solution" but I am not sure it is correct :-)
override fun editNote(note: Note): Completable {
return Observable.just(note)
.map { mapper.mapToEntity(it) }
.flatMapCompletable { noteEntity ->
factory.getCacheDataStore().editNote(noteEntity)
.andThen { factory.getRemoteDataStore().editNote(noteEntity) }
}
}
You're looking for flatMapCompletable instead of map, because map just intercepts the stream and maps the emissions to another type, while 'flatMap' (or it's siblings), from the docs:
Transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable.
You can see it's marble diagram in Here
I have a simple Android application with Room database and I am trying to react to an #Insert query with RxJava but I am unable to chain the calls correctly.
This is my view model method calling the insert:
fun insertTopic(): Single<Long> {
val topic = Topic(null, topicText.value!!, difficulty.value!!, false)
return Single.create<Long> { Observable.just(topicDao.insert(topic)) }
}
And this is the code in my activity triggering the save action:
disposable.add(RxView.clicks(button_save)
.flatMapSingle {
viewModel.insertTopic()
.subscribeOn(Schedulers.io())
}.observeOn(AndroidSchedulers.mainThread())
.doOnError { Toast.makeText(this, "Error inserting topic", Toast.LENGTH_SHORT).show() }
.subscribe { id ->
// NOT INVOKED
hideKeyboard()
Toast.makeText(this, "Topic inserted. ID: $id", Toast.LENGTH_SHORT).show()
this.finish
})
When I click the button, the entity is saved but none of the subscribe code is invoked (no toast is shown). Could someone point out to me what am I doing wrong? I am fairly new to RX java.
The problem is in incorrect usage of Single.create. There is no need in wrapping topicDao.insert(topic) into Observable. Moreover, you are implementing new Single manually, which means you must pass the result id to the #NonNull SingleEmitter<T> emitter argument. But there is no need in using Single.create here.
Single.fromCallable is exactly what you need:
fun insertTopic(): Single<Long> = Single.fromCallable {
val topic = Topic(null, topicText.value!!, difficulty.value!!, false)
return#fromCallable topicDao.insert(topic)
}
Please note, that I create topic object inside lambda so that it isn't captured in a closure. Also keep in mind that fromCallable may throw UndeliverableException if you unsubscribe from Single during the lambda code execution. It will probably never happen in your specific case, but you should understand RxJava2 error handling design.
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
} )
I have a code like this:
service.getUserById(10)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.concatMap(getFullUserFromDto())
.subscribe(doSomehingWithUser());
private Func1<UserDto, Observable<User>> getFullUserFromDto() {
return new Func1<UserDto, Observable<User>>() {
#Override
public Observable<User> call(final UserDto dto) {
return dao.getUserById(dto.getUserId());
}
};
}
and in my DAO, I have:
public Observable<User> getUserById(final Long id) {
return api.getUserById(id).map(//more things...
}
Note there are two levels of "concatenation": service -> dao -> api. Method api.getUserById(id) make a network call.
I'm getting NetworkOnMainThreadException error. Why? I'm using and subscribeOn and observeOn operators, but it seems that it is not applied to the "final" built Observable.
If I use this operators in the API call, in the DAO, it works:
return api.getUserById(id)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(//more things...
Is there a way to use just once in the "root" Observable?
So, concatMap subscribes on Observables. What thread is used to perform this operation? Well, the thread that called onNext for the concatMat, given that it doesn't change threads/schedulers. So, one simple transposition should help with this:
service.getUserById(10)
.subscribeOn(Schedulers.newThread())
.concatMap(getFullUserFromDto())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(doSomehingWithUser());
I'd also suggest to use Schedulers.io(), as it will re-use threads.
Short answer: use observeOn before chained operations to controll on which schedulers they are executed:
service.getUserById(10)
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.io())
.concatMap(getFullUserFromDto())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(doSomehingWithUser());
In the example above, .concatMap will be executed in Schedulers.io()
More details can be found here:
http://tomstechnicalblog.blogspot.com/2016/02/rxjava-understanding-observeon-and.html