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'
}}
Related
I have This method that calls a Rest API and returns the result as an Observable (Single):
fun resetPassword(email: String): Single<ResetPassword> {
return Single.create { emitter ->
val subscription = mApiInterfacePanda.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({ resetPasswordResponse ->
when(resetPasswordResponse.code()) {
200 -> {
resetPasswordResponse?.body()?.let { resetPassword ->
emitter.onSuccess(resetPassword)
}
}
else -> emitter.onError(Exception("Server Error"))
}
}, { throwable ->
emitter.onError(throwable)
})
mCompositeDisposable.add(subscription)
}
}
Unit Test:
#Test
fun resetPassword_200() {
val response = Response.success(200, sMockResetPasswordResponse)
Mockito.`when`(mApiInterfacePanda.resetPassword(Mockito.anyString()))
.thenReturn(Single.just(response))
mTokenRepository.resetPassword(MOCK_EMAIL)
val observer = mApiInterfacePanda.resetPassword(MOCK_EMAIL)
val testObserver = TestObserver.create<Response<ResetPassword>>()
observer.subscribe(testObserver)
testObserver.assertSubscribed()
testObserver.awaitCount(1)
testObserver.assertComplete()
testObserver.assertResult(response)
}
My Problem is only this line gets covered and the other lines won't run and that has a lot of impact on my total test coverage:
return Single.create { emitter ->
There's more than one thing going on here if I'm not mistaken. Let's take it in parts.
First, your "internal" observer:
mApiInterfacePanda.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({ resetPasswordResponse -> ... })
Is observing on the android main thread and executing on a background thread. To the best of my knowledge, in most cases, the test thread will end before your mApiInterfacePanda .resetPassword has a chance to finish and run. You didn't really post the test setup, so I'm not sure if this is an actual issue, but in any case it's worth mentioning. Here's 2 ways to fix this:
RxJavaPlugins and RxAndroidPlugins
RxJava already provides a way to change the schedulers that are provided. An example is RxAndroidPlugins.setMainThreadSchedulerHandler. Here's how it could help:
#Before
fun setUp() {
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setInitIoSchedulerHandler { Schedulers.trampoline() }
}
The above methods make sure that everywhere you use the main thread scheduler and the io scheduler, it'll instead return the trampoline scheduler. This is a scheduler that guarantees that the code is executed in the same thread that was executing previously. In other words, it'll make sure you run it on the unit test main thread.
You will have to undo these:
#After
fun tearDown() {
RxAndroidPlugins.reset()
RxJavaPlugins.reset()
}
You can also change other schedulers.
Inject the schedulers
You can use kotlin's default arguments to help out with injecting schedulers:
fun resetPassword(
email: String,
obsScheduler: Scheduler = AndroidSchedulers.mainThread(),
subScheduler: Scheduler = Schedulers.io()
): Single<ResetPassword> {
return Single.create { emitter ->
val subscription = mApiInterfacePanda.resetPassword(email)
.observeOn(obsScheduler)
.subscribeOn(subScheduler)
.subscribe({ resetPasswordResponse ->
when(resetPasswordResponse.code()) {
200 -> {
resetPasswordResponse?.body()?.let { resetPassword ->
emitter.onSuccess(resetPassword)
}
}
else -> emitter.onError(Exception("Server Error"))
}
}, { throwable ->
emitter.onError(throwable)
})
mCompositeDisposable.add(subscription)
}
}
At test time you can just call it like resetPassword("foo#bar.com", Schedulers.trampoline(), Schedulers.trampoline() and for the application just pass in the email.
The other thing I see here is maybe not related to the problem, but I think it's still good to know. First, you're creating a single, but you don't need to do this.
Single.create is usually used when you don't have reactive code. However, mApiInterfacePanda.resetPassword(email) already returns a reactive component and although I'm not sure, let's just assume it's a single. If not, it should be fairly simple to convert it to something else.
You're also holding on to a disposable, which from what I can tell shouldn't be necessary.
Lastly, you're using retrofit according to your tags so you don't need to make the call return a raw response unless extremely necessary. This is true because retrofit checks the status code for you and will deliver the errors inside onError with an http exception. This is the Rx way of handling the errors.
With all this in mind, I'd rewrite the entire method like this:
fun resetPassword(email: String) = mApiInterfacePanda.resetPassword(email)
(note that resetPassword must not return a raw response, but Single<ResetPassword>
It actually shouldn't need anything else. Retrofit will make sure things end up in either onSuccess or onError. You don't need to subscribe to the result of the api here and handle disposables - let whoever is calling this code handle it.
You may also notice that if this is the case, then the solution for the schedulers is not needed. I guess this is true in this case, just remember some operators operate in some default schedulers and you may need to override them in some cases.
So how would I test the above method?
Personally I'd just check if the method calls the api with the right parameters:
#Test
fun resetPassword() {
mTokenRepository.resetPassword(MOCK_EMAIL)
verify(mApiInterfacePanda).resetPassword(MOCK_EMAIL)
}
I don't think there's much more needed here. There's no more logic I can see in the rewritten method.
I am not expert in Rx sorry if it is trivial question so.I am doing a polling operation which I have to wait for updates and I created Rx Observable for that; however, I never call onComplete. but in onDestroy I unsubscribe. please take a look on the below code.
#Reusable
class PollingExample #Inject constructor() {
var itemObservable: Observable<List<Item>>
private set
private lateinit var itemObservableEmitter: WeakReference<ObservableEmitter<List<Item>>>
init {
itemObservable = Observable.create { e -> itemObservableEmitter = WeakReference(e) }
}
fun submitData(items: List<Item>) {
itemObservableEmitter.get()?.onNext(items)
}
}
is it Valid to do something like that
After some search I think as long as I do not want to call onError() or onComplete() it is better to use this library https://github.com/JakeWharton/RxRelay which guarantee that everything will continue working without the worry of accidentally triggering a terminal state
I'm having trouble with testing the manual manipulation of disposables inside my class being tested. I have a itemsProcessed map that keeps track of the different disposables that are created, the reason is that some disposables I need to be able to dispose on demand, and others I just need to know they exist.
My class runs fine and everything works as expected, but in my tests, I noticed that the disposables that I make aren't being inserted into my map until what would be the async code is already completed.
I'm not sure if it has anything to do with the fact that I'm using
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
Here are the relevant methods I'm testing, the myRepo.processItem() call is on a background thread
private fun processItem(item: Item) {
itemsProcessed[item] = myRepo.processItem(item)
.doOnComplete {
safelyDelete(item)
itemsProcessed.remove(item)
}
.doOnError {
itemsProcessed.remove(item)
}
.subscribe({}, {})
}
private fun cleanOldItems() {
itemList.forEach {
if (!itemsProcessed[item].exist())
safelyDelete(it)
}
}
myRepo has a processor called itemProcessor which calls the method above, my test is as follows
#Test
fun doNotDeleteItemsBeingProcessed() {
`when`(itemProcessor.processItem(any()))
.thenAnswer {
//from my understanding of disposables, the disposable should have been made in my real class and should have been inserted into the map at this point
trigger cleanOldItems
Completable.timer(5000, TimeUnit.MILLISECONDS)
}
repo.triggerProcessItems()
Assert.assertTrue(itemList.contains(item))
}
It seems like when I run the test, itemsProcessed map in my class is empty until the last assert line in my test is reached. When I added in doOnSubscribe, I noticed that doOnSubscribe was also called at the very end, what's causing this behaviour?
What does trigger cleanOldItems do? Assuming this is a synchronous call, then it'll call your cleaning function before it returns any disposable to store in your map. Instead, you should have
#Test
fun doNotDeleteItemsBeingProcessed() {
`when`(itemProcessor.processItem(any()))
.thenAnswer {
//from my understanding of disposables, the disposable should have been made in my real class and should have been inserted into the map at this point
Completable.timer(5000, TimeUnit.MILLISECONDS)
}
repo.triggerProcessItems()
repo.triggerCleanOldItems()
Assert.assertTrue(itemList.contains(item))
}
Also bear in mind that Completable.timer call uses computation scheduler internally, so unless you explicitly provide a scheduler to it (as in Completable.timer(5000, TimeUnit.MILLISECONDS, Schedulers.trampoline()) or override setComputationSchedulerHandler your subscription will be triggered on a different thread.
I am try to use the following code
initLocalSettingsIfNeed()
.andThen(initGlobalSettingsIfNeed(configuration))
.doOnComplete(callback::onSuccess)
.doOnError(throwable -> callback.onError(throwable.getLocalizedMessage()))
.subscribe();
But I have exception
The exception was not handled due to missing onError handler in the
subscribe() method call.
I guess I am not using this methods correctly, I thought can replace doOnComplete doOnError with observer inside subscribe() method, I am wrong?
Regarding your original question, you have to know that doOnError is not a replacement of onError. You have a good and short explanation about it in this blog:
Actually there’s one key difference between them. doOnError() basically only triggers its callback, then passes down the encountered errors to the down stream. So if the whole stream is subscribed without the onError callback in subscribe(), your app will crash by OnErrorNotImplementedException.
The onError callback in subscribe() in the other hand does consume the
errors. That means, it will catch the errors, and let you handle them
without re-throwing the errors by itself.
About the warning you mention in one comment:
This approach is working, but i have warning 'the result of subscribe
not used', as i know this need to be disposed automatically when
onError or onComplete is called, is there way to avoid this warning? – Pavel Poley
A good approach is that your methods inside your Repository return a Observable, and then you can subscribe to them in your ViewModel. Then, in every ViewModel class you can have a member variable with a CompositeDisposable where you can add the disposable of each subscription to the Observables returned by your repository. Finally, you should override the onCleared method to dispose all the disposables stored in the CompositeDisposable.
public class MyViewModel extends ViewModel {
private MyRepository myRepository;
private final CompositeDisposable disposables;
#Inject
public MyViewModel(MyRepository myRepository) {
...
this.myRepository = myRepository;
disposables = new CompositeDisposable();
...
}
public void callObservableInRepository() {
disposables.add(myRepository.myObservable()
.subscribe(onSuccess -> {...} , onError -> {...}));
}
#Override
protected void onCleared() {
disposables.clear();
}
}
try this
initLocalSettingsIfNeed()
.andThen(initGlobalSettingsIfNeed(configuration))
.subscribe({completed->
callback.onSuccess()
},{throwable->
callback.onError(throwable.getLocalizedMessage())
})
When going through a lot of articles that talk about Android MVVM architectures with RxJava 2 (such as this article), you often see something that looks like the following:
class LobbyViewModel extends ViewModel {
...
void loadCommonGreeting() {
loadGreeting(loadCommonGreetingUseCase.execute());
}
void loadLobbyGreeting() {
loadGreeting(loadLobbyGreetingUseCase.execute());
}
MutableLiveData<Response<String>> getResponse() {
return response;
}
MutableLiveData<Boolean> getLoadingStatus() {
return loadingStatus;
}
private void loadGreeting(Single<String> single) {
disposables.add(single
.subscribeOn(schedulersFacade.io())
.observeOn(schedulersFacade.ui())
.doOnSubscribe(s -> loadingStatus.setValue(true))
.doAfterTerminate(() -> loadingStatus.setValue(false))
.subscribe(
greeting -> response.setValue(Response.success(greeting)),
throwable -> response.setValue(Response.error(throwable))
)
);
}
When you look at the logic of the above, you see that every time the user triggers an action, (loadCommonGreeting()/loadLobbyGreeting()) a new disposable will be added to the CompositeDisposable variable disposables.
In this case it probably won't be a huge deal, but I can imagine that maybe in some other scenarios the number of Disposables linked to the CompositeDisposables could run up to the thousands or more.
Is this considered a good or safe practice?
Assuming that each subscription in the CompositeDisposable completes, there will be only a small amount of memory leakage over time.
If you are concerned about it, you should be able to create the disposable ahead of time in loadGreeting(). Add a doOnUnsubscribe() operation that will remove the disposable from the CompositeDisposable().