I'm trying to use Realm with RxJava and Retrofit in a way DanLew described here concating input from realm and retrofit but it gets stuck if I adding realm into the chain
Observable.concat(countryStorage.restoreAsObservable(),
networkService.api()
.getCountries()
.doOnNext(countryStorage::save))
.first()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(//never reaching here)
storage
#Override public Observable<List<Country>> restoreAsObservable() {
Realm realm = realmProvider.get();
return realm.where(Country.class)
.findAll()
.asObservable()
.map(countries -> return realm.copyFromRealm(countries))
.first(countries -> return !countries.isEmpty())
.doOnCompleted(realm::close());
}
It seems that this could happen that observable is hot from Realm, but nothing about it in the docs and how I suppose to compose Realm with other observables?
UPDATE:
It turns to be that it works fine in old way. The question still remain about new api.
return Observable.just(
realm.copyFromRealm(realm.where(Country.class).findAll()))
.filter(countries -> !countries.isEmpty())
.doOnCompleted(realm::close);
It is happening because countryStorage.restoreAsObservable() never completes and if you read concat doc, it explicitly states that:
Concat waits to subscribe to each additional Observable that you pass to it until the previous Observable completes.
Instead you can just do something like:
countryStorage.restoreAsObservable()
.doOnSubscribe(() -> {
networkService.api()
.getCountries()
.subscribe(countryStorage::save)
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(//do smth)
Related
I'm trying to learn to use Rxjava to make Api calls with retrofit. I have to make multiple api calls in a loop. I'm struggling with getting the value in my subscriber.
#GET("pokemon" + "/{id}")
fun getPokemonData(#Path("id") id: Int):
Observable<Pokemon>
I'm expecting to get a Pokemon object in my Subscriber but instead I get a Observable. How do I transform it to a Pokemon object?
Observable.fromIterable(list)
.flatMap { it ->
Observable
.just(it.url)
.map { PokeApi.retrofitService.getPokemonData(getPokemonIdFromUrl(it))
}
}
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
//onNext --I'm expecting to get a Pokemon Object here, instead I get a Observable<Pokemon>
}, {//onError} , {// do something when all api calls are done?})
My goal is to make api calls with ids in the "list" and get "notified" when all the api calls are finished. Is this the correct approach to solve this problem ?
The problems lies here:
Observable
.just(it.url)
.map { PokeApi.retrofitService.getPokemonData(getPokemonIdFromUrl(it)) }
When you use map it maps to the return object from getPokemonData. You probably want to flatMap it:
Observable
.just(it.url)
.flatMap { PokeApi.retrofitService.getPokemonData(getPokemonIdFromUrl(it)) }
which not only maps the result but flattens it too so you don't get an observable, but the result of that observable.
I've upgraded to Android Studio 3.1 today, which seems to have added a few more lint checks. One of these lint checks is for one-shot RxJava2 subscribe() calls that are not stored in a variable. For example, getting a list of all players from my Room database:
Single.just(db)
.subscribeOn(Schedulers.io())
.subscribe(db -> db.playerDao().getAll());
Results in a big yellow block and this tooltip:
The result of subscribe is not used
What is the best practice for one-shot Rx calls like this? Should I keep hold of the Disposable and dispose() on complete? Or should I just #SuppressLint and move on?
This only seems to affect RxJava2 (io.reactivex), RxJava (rx) does not have this lint.
The IDE does not know what potential effects your subscription can have when it's not disposed, so it treats it as potentially unsafe. For example, your Single may contain a network call, which could cause a memory leak if your Activity is abandoned during its execution.
A convenient way to manage a large amount of Disposables is to use a CompositeDisposable; just create a new CompositeDisposable instance variable in your enclosing class, then add all your Disposables to the CompositeDisposable (with RxKotlin you can just append addTo(compositeDisposable) to all of your Disposables). Finally, when you're done with your instance, call compositeDisposable.dispose().
This will get rid of the lint warnings, and ensure your Disposables are managed properly.
In this case, the code would look like:
CompositeDisposable compositeDisposable = new CompositeDisposable();
Disposable disposable = Single.just(db)
.subscribeOn(Schedulers.io())
.subscribe(db -> db.get(1)));
compositeDisposable.add(disposable); //IDE is satisfied that the Disposable is being managed.
disposable.addTo(compositeDisposable); //Alternatively, use this RxKotlin extension function.
compositeDisposable.dispose(); //Placed wherever we'd like to dispose our Disposables (i.e. in onDestroy()).
The moment the Activity will be destroyed, the list of Disposables gets cleared and we’re good.
io.reactivex.disposables.CompositeDisposable mDisposable;
mDisposable = new CompositeDisposable();
mDisposable.add(
Single.just(db)
.subscribeOn(Schedulers.io())
.subscribe(db -> db.get(1)));
mDisposable.dispose(); // dispose wherever is required
You can subscribe with DisposableSingleObserver:
Single.just(db)
.subscribeOn(Schedulers.io())
.subscribe(new DisposableSingleObserver<Object>() {
#Override
public void onSuccess(Object obj) {
// work with the resulting todos...
dispose();
}
#Override
public void onError(Throwable e) {
// handle the error case...
dispose();
}});
In case you need to directly dispose Single object (e.g. before it emits) you can implement method onSubscribe(Disposable d) to get and use the Disposable reference.
You can also realize SingleObserver interface by your own or use other child classes.
As was suggested you may use some global CompositeDisposable to add the result of the subscribe operation there.
The RxJava2Extensions library contains useful methods to automatically remove created disposable from the CompositeDisposable when it completes. See subscribeAutoDispose section.
In your case it may look like this
SingleConsumers.subscribeAutoDispose(
Single.just(db)
.subscribeOn(Schedulers.io()),
composite,
db -> db.playerDao().getAll())
You can use Uber AutoDispose and rxjava .as
Single.just(db)
.subscribeOn(Schedulers.io())
.as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this)))
.subscribe(db -> db.playerDao().getAll());
Make sure that you understand when you unsubscribe based on the ScopeProvider.
Again and again I find myself coming back to the question of how to correctly dispose of subscriptions, and to this posting in particular. Several blogs and talks claim that failing to call dispose necessarily leads to a memory leak, which I think is a too general statement. In my understanding, the lint warning about not storing the result of subscribe is a non-issue in some cases, because:
Not all observables run in the context of an Android activity
The observable can be synchronous
Dispose is called implicitly, provided the observable completes
Since I don't want to suppress lint warnings I recently started to use the following pattern for cases with a synchronous observable:
var disposable: Disposable? = null
disposable = Observable
.just(/* Whatever */)
.anyOperator()
.anyOtherOperator()
.subscribe(
{ /* onSuccess */ },
{ /* onError */ },
{
// onComplete
// Make lint happy. It's already disposed because the stream completed.
disposable?.dispose()
}
)
I'd be interested in any comments on this, regardless of whether it's a confirmation of correctness or the discovery of a loophole.
There's another way available, which is avoiding to use Disposables manually (add and remove subscriptions).
You can define an Observable and that observable is going to receive the content from a SubjectBehaviour (in case you use RxJava). And by passing that observable to your LiveData, that should work. Check out the next example based on the initial question:
private val playerSubject: Subject<Player> = BehaviorSubject.create()
private fun getPlayer(idPlayer: String) {
playerSubject.onNext(idPlayer)
}
private val playerSuccessful: Observable<DataResult<Player>> = playerSubject
.flatMap { playerId ->
playerRepository.getPlayer(playerId).toObservable()
}
.share()
val playerFound: LiveData<Player>
get() = playerSuccessful
.filterAndMapDataSuccess()
.toLiveData()
val playerNotFound: LiveData<Unit>
get() = playerSuccessful.filterAndMapDataFailure()
.map { Unit }
.toLiveData()
// These are a couple of helpful extensions
fun <T> Observable<DataResult<T>>.filterAndMapDataSuccess(): Observable<T> =
filter { it is DataResult.Success }.map { (it as DataResult.Success).data }
fun <T> Observable<DataResult<T>>.filterAndMapDataFailure(): Observable<DataResult.Failure<T>> =
filter { it is DataResult.Failure }.map { it as DataResult.Failure<T> }
If you are sure that disposable handled correctly, for example using doOnSubscribe() operator, you may add this to Gradle:
android {
lintOptions {
disable 'CheckResult'
}}
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);
}
});
}
What is the proper way of querying and returning an unmanaged result of items with realm, everything in the background thread?. I'm using somethibf like this:
return Observable.just(1)
.subscribeOn(Schedulers.io())
.map {
val realm = Realm.getDefaultInstance()
val results = realm.where(ItemRealm::class.java)
.equalTo("sent", false).findAll()
realm to results
}
.map {
val (realm, results) = it
val unManagedResults = realm.copyFromRealm(results)
realm.close()
unManagedResults
}
}
And then chaining this observable with another one that will post the results to a server.
The solution working, although is a bit ugly on this aspects:
No proper way of wrapping the realmQuery in an observable, because
there is no way of opening a realInstance in a background thread without this kind of cheat (at least that i know about), so i need to use this fake
observable Observable.just(1).
Not the best place to open and close Realm instances, inside first and second map
I don't know if it is guaranteed that the realm instance is closed after all the items have been copied.
So what is the proper way of Query and Return unmanaged results on the background thread (some context, i need this to send the results to a server, in the background and as this task is totally independent from my app current data flow, so it should be off the main thread).
Suggested Version:
return Observable.fromCallable {
Realm.getDefaultInstance().use { realm ->
realm.copyFromRealm(
realm.where(ItemRealm::class.java)
.equalTo(ItemRealm.FIELD_SEND, false).findAll()
)
}
}
This is how you would turn your Realm objects unmanaged:
return Observable.defer(() -> {
try(Realm realm = Realm.getDefaultInstance()) {
return Observable.just(
realm.copyFromRealm(
realm.where(ItemRealm.class).equalTo("sent", false).findAll()
)
);
}
}).subscribeOn(Schedulers.io());
Although this answer is Java, the Kotlin answer is just half step away.
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