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
} )
Related
I'm trying to understand the logic of switchIfEmpty operator. I will be very thankful for every explanation.
I have a local database (Room) and remote server. My goal is to implement logic with switchIfEmpty to check if there is data in local DB to take it and if local DB is empty to call from remote. The process starts in activity where I subscribe to Observable:
private fun subscribeOnDataChanges() = with(viewModel) {
requestNextPage()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
filmsAdapter.addItems(it)
}, {
errorEvent.value = ERROR_MSG
it.printStackTrace()
}).addTo(autoDisposable)
}
Then the methods in the View Model:
fun requestNextPage(): Observable<List<Film>> {
return requestPageOfFilms()
}
private fun requestPageOfFilms(): Observable<List<Film>> =
interactor.requestPageOfFilmsFromDataSource()
And, finally the method with switchIfEmpty in the Interactor:
fun requestPageOfFilmsFromDataSource(): Observable<List<Film>> {
return repo.getPageOfFilmsInCategory(category).filter { it.isNotEmpty() }.switchIfEmpty(
getFromRemote(category)
)
}
private fun getFromRemote(category: String): Observable<List<Film>> {
return convertSingleApiToObservableDtoList(
retrofitService.getFilms(
category, API.KEY, "ru-RU", NEXT_PAGE
)
)
}
I cannot understand the next things:
Why, when local db (repo) is NOT empty, getFromRemote() is called?
If local db is empty, why network call in the method getFromRemote() is not performed? No matters, that I subscribed in the activity? Because if I add the subscription inside the switchIfEmpty(), the network call is performed.
To answer your questions :
Why, when local db (repo) is NOT empty, getFromRemote() is called?
Because this function convertSingleApiToObservableDtoList() is being evaluated at Assembly Time and not Subscription Time (https://github.com/ReactiveX/RxJava#assembly-time). The result is that the Observable object is eagerly evaluated.
If local db is empty, why network call in the method getFromRemote() is not performed? No matters, that I subscribed in the activity? Because if I add the subscription inside the switchIfEmpty(), the network call is performed.
Because the upstream has not emitted a value yet or completed. You are conflating an empty List with an empty Observable (https://reactivex.io/documentation/operators/defaultifempty.html)
A better solution to your problem would be using the flatMap or even switchMap operator if you expect a chatty upstream and only care about latest result, something like :
fun requestPageOfFilmsFromDataSource(): Observable<List<Film>> {
return repo.getPageOfFilmsInCategory(category)
.flatMap { films ->
if (films.isNotEmpty()) {
Observable.just(films)
} else getFromRemote(category)
}
}
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've a TestService, where I do an async task to get my data. I would like to wait for the response before I continue.
public List<Data> getData() {
List<Data> data = new ArrayList<>();
Disposable disposable = repository.getDataFromApi(false)
.observeOn(AndroidSchedulers.mainThread())
.subscribe( newData -> {
data.addAll(newData);
}, __ -> { });
mCompositeDisposable.add(disposable);
//Here I want to stop till "Data" arraylist is filled with data
... do something with data
}
In Volley I could just call req.executeSynchronously(); to make it happen. As getData() have to return data already, I've to somehow make it wait till I get response. How to do it? I'm using Single.
My approach using getBlocking();
public List<Data> getData() {
List<Data> data = new ArrayList<>();
Disposable disposable = repository.getDataFromApi(false)
.observeOn(AndroidSchedulers.mainThread())
.blockingGet();
.subscribe( newData -> {
data.addAll(newData);
}, __ -> { });
mCompositeDisposable.add(disposable);
//Here I want to stop till "Data" arraylist is filled with data
... do something with data
}
It says cannot resolve method subscribe, so I'm probably calling it wrong..
fun getDataFromApi(): Single<List<Data>> {
return service.getData()
.map { jsonApiObject ->
...
return#map data
}
}
Hopefully you are aware that blocking is a strong antipattern in RxJava and you should avoid blocking whenever you can.
Saying that, if you really need to block, you have two options:
use blockingGet() which - as the name indicates - blocks current thread and directly returns value of publisher (Single in your case). This is probably what you were looking for. In your case:
newData = repository.getDataFromApi(false).blockingGet();
data.addAll(newData);
synchronize with Java classes, like CountDownLatch - more complicated and I would use blockingGet() because it's more straightforward. But it's a possibility.
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);
}
});
}