'RxJava Single - Getting a memory leak, how to correctly unsubscribe?

I'm using RxJava's Single.fromCallable() to wrap around a third party library that makes an API call. I was testing different states on the call, success, failed, low network, no network.

But on the no network test I ran into a memory leak for the first time ever using RxJava. I spent the last hour combing through the code and trying to narrow down the leak with the LeakCanary library.

I figured out it was coming from subscribing to the Single.fromCallable().

Single.fromCallable(() -> {
       return remoteRepository.makeTransaction(signedTransaction).execute();
})
       .subscribeOn(Schedulers.newThread())
       .observeOn(AndroidSchedulers.mainThread())
       .subscribe(txHash -> {
              Log.d(TAG, "makeTransaction: " + txHash);
                    
                   
       }, err -> {
             Log.e(TAG, "makeTransaction: ", err);
                    
       });

Once I remove the

.subscribe(txHash -> { ... });

It no longer leaks.

I've tried googling stackoverflow with RxJava Single unsubscribe and I'm getting answers saying that you don't need to unsubscribe from Single

https://stackoverflow.com/a/43332198/11110509.

But if I don't I'll be getting memory leaks.

I've tried to unsubscribe by making the Single call an instance variable in my ViewModel:

Disposable mDisposable;

mDisposable = Single.fromCallable(() -> {...}).subscribe(txHash -> {..});

and unsubscribing it in the Fragment's onDestroy() method so it will unsubscribe if the user exits the screen before the call is finished.

@Override
    public void onDestroy() {
        super.onDestroy();
        mViewModel.getDisposable().dispose();
    }

But it's still leaking. I'm not sure if this is the correct way to unsubscribe or maybe I'm doing something else incorrectly.

How can I correctly unsubscribe from the Single Callable?

Edit:_________________________________

How I'm recreating the issue:

Screen one is launches screen two. The call is performed instantly on the creation of screen two. Since I'm testing it with no network, the query is continuing to perform on screen two until it times out. So closing the screen two before the call finishes causes the leak.

It doesn't seem to be a context leak because I've removed tried testing it by removing all the methods inside the .subscribe():

Single.fromCallable(() -> {
           
            return remoteRepository.makeTransaction(signedTransaction).execute();
})
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(txHash-> {
              //Removed all methods here.
              //Still leaks.

         }, err -> {

                    
         });

But when I remove:

.subscribe(txHash-> {
                   
}, err -> {  
              
});

it no longer leaks.

LeakCanary logs:

┬───
    │ GC Root: Java local variable
    │
    ├─ java.lang.Thread thread
    │    Leaking: UNKNOWN
    │    Retaining 2.4 kB in 81 objects
    │    Thread name: 'RxCachedThreadScheduler-2'
    │    ↓ Thread.<Java Local>
    │             ~~~~~~~~~~~~
    ╰→ com.dave.testapp.ui.send.SendViewModel instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.dave.testapp.ui.send.SendViewModel received
    ​     ViewModel#onCleared() callback)
    ​     Retaining 588 B in 19 objects
    ​     key = 6828ea76-a75c-448b-8278-d0e0bb0229c8
    ​     watchDurationMillis = 10324
    ​     retainedDurationMillis = 5321
    ​     baseApplication instance of com.dave.testapp.BaseApplication
    
    METADATA
    
    Build.VERSION.SDK_INT: 27
    Build.MANUFACTURER: Google
    LeakCanary version: 2.7
    App process name: com.dave.testapp


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source