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().
Related
I'm not an expert on RxJava sorry if my question is a dummy question, I notice that my project is using RxLifeCycle dependency, I want to rid of it from the project due to the author advice, I wonder how I should remove it, it's just enough to create a composite disposable and dispose all my disposables at onDestroy() method? or is necessary to something else?
Thank you so much.
EDIT:
I provide some examples of the code
First, everything inherits from a base activity, this activity has this method which is used across all activities
public <T> Observable<T> bindRxToActivity(Observable<T> observable, boolean isSilent) {
if (!isSilent) {
loading(true);
hideKeyboard();
}
return observable
.compose(bindToLifecycle())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.doOnEach(r -> handleDoOnEach())
.doOnNext(this::handleDoOnNext);
}
And it is used like this:
bindRxToActivity(intentApi.evaluation(), false)
.subscribe(
{ this.handleTransferEvaluationSuccess(it) },
{ this.evaluateTransferFailed(it) }
)
In all scenarios of the activities is used as above.
and for fragments:
public <T> Observable<T> bindRxToFragment(Observable<T> observable, boolean silent) {
if (!silent) {
this.loading(true);
listener.hideKeyboard();
}
return observable
.compose(bindToLifecycle())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.doOnEach(r -> handleDoOnEach())
.doOnNext(this::handleDoOnNext);
}
And it is used like this in all fragments:
baseFragment.bindRxToFragment(baseFragment.r.loginApi().login(login), true)
.subscribe(
login1 -> loginSuccess(BaseActivity, login1, listener),
baseFragment::handleError
);
So what could be the best approach to use manual disposing of the observers?
Thanks
I'd like to use the same LiveData with different sources. One from an API call which is an observable and one from a database which is a LiveData. I'd like to be able to do something like this:
private LiveData<List<Items>> items = new MutableLiveData<>();
// this one comes from an API and it's an observable
public void onApiItemsSelected(String name) {
Disposable disposable = repository.getItemsFromApi(name)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(itemsList -> {
items.setValue(itemsList);
});
compositeDisposable.add(disposable);
}
// this one comes from the database and it's a livedata that I want to transform first
public void onDatabaseItemsSelected() {
items = Transformations.map(repository.getItemsFromdatabase(), itemsList -> {
List<Items> finalItemsList = new ArrayList<>();
for (Item item : itemsList) {
finalItemsList.add(itemsList.toSomething());
}
return finalItemsList;
});
}
The problem is that a Transformation.map always returns a LiveData and in order to make a items.setValue(itemsList) items need to be a MutableLiveData. I tried with MediatorLiveData but it's calling the two sources and mixing everything. That's not what I need here. I need one source OR the other. Is it possible?
I think you could delegate this responsibility to your repository.
See for example this open source project and related article with more info.
Basically, the repository handles all the complexity of deciding which source to get the data from (see the JobRepository). It exposes a rx Observable to the ViewModel (see the JobsViewModel). Then all the ViewModel has to do is update the LiveData:
private val presentation: MutableLiveData<Presentation> = MutableLiveData()
fun bind() {
jobRepository.getJobs(true)
.subscribeOn(Schedulers.io())
.subscribe { resource: Resource<List<JobWithRelations>> ->
when (resource) {
// commenting some parts for brevity ...
is Resource.ResourceFound -> {
presentation.postValue(Presentation(getApplication(), resource.data!!))
}
// ...
}
}.addTo(compositeDisposable)
}
Managing multiple sources of data is complex. Among the many aspects involved in architecting this, things to keep in mind include:
In-memory caching expiration strategies
Policies for db caching, for example, when users are offline
Throttling and retry of API calls
Policies for updating the in-memory cache or db when new data comes from the network
One library that could help you is the Dropbox Store. With it, you could build your data sources like in the example below from their documentation:
StoreBuilder
.from(
fetcher = nonFlowValueFetcher { api.fetchSubreddit(it, "10").data.children.map(::toPosts) },
sourceOfTruth = SourceOfTrue.from(
reader = db.postDao()::loadPosts,
writer = db.postDao()::insertPosts,
delete = db.postDao()::clearFeed,
deleteAll = db.postDao()::clearAllFeeds
)
).build()
Then to get the data in your ViewModel:
private val presentation: MutableLiveData<Presentation> = MutableLiveData()
lifecycleScope.launchWhenStarted {
store.stream(StoreRequest.cached(key = key, refresh=true)).collect { response ->
when(response) {
// commenting some parts for brevity ...
is StoreResponse.Data -> {
presentation.postValue(response.value)
}
// ...
}
}
}
I am not sure it is the best practice but I think will do the trick.
public void onDatabaseItemsSelected() {
items.value = repository.getItemsFromdatabase().value;
};
The syntax may not be accurate as I am know little about java, I do mainly kotlin. But the thing is: when the user clicks Api, make your onApiItemsSelected() run, that will set the value of items, when the user clicks database, make onDatabaseItemsSelected() run, so it will replace items value for the result of repository.getItemsFromDatabase()
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'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'
}}