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
Related
I've a livedata which emits everytime there is a update in the database. When the particular screen opens, this livedata emits immediately with whatever value is there in the database. Then, a network call is made to update the database. After the database is updated, the livedata emits again. This leads to two emissions in very quick succession. Subsequent updates to the database work properly cz there is only one emission whenever the database is updated. Only the first time, there are 2 updates in very quick succession. I want to avoid that.
An idea to avoid that would be something like this. When the livedata emits, wait for Xs. If there is another emission in those Xs, discard the data from old emission and use the new one. Wait for Xs again. If there is no emission in those Xs, use the latest data.
This looks very similiar to throttling but only once. I was wondering if there's a simple way to do something like using LiveData or MediatorLiveData.
You can post delayed Runnable with timeout you want after first LiveData event.
Every LiveData update remove posted Runnable and post it again.
You can use MediatorLiveData and a boolean val for achieving this.
Create a mDbLiveData, mediator livedata mFinalLiveData and boolean mLoadedFromAPI when data from API is loaded.
On API success or failure, set mLoadedFromAPI to true;
Observe mFinalLiveData in your Activity/Fragment
LiveData<Model> mDbLiveData;
MediatorLiveData<Model> mFinalLiveData = new MediatorLiveData();
private boolean mLoadedFromAPI = false;
// Load db data in mDbLiveData
mDbLiveData = // Data from DB
// Add mDbLiveData as source in mFinaliveData
mFinalLiveData.addSource(mDbLiveData, dbData -> {
if (mLoadedFromAPI) mFinalLiveData.postValue(dbData);
});
This post helped. https://medium.com/#guilherme.devel/throttle-operator-with-livedata-and-kotlin-coroutines-ec42f8cbc0b0
I modified the solution a bit to fit my usecase:
fun <T> LiveData<T>.debounceOnce(duration: Long,
coroutineContextProvider: CoroutineContextProvider): LiveData<T> {
return MediatorLiveData<T>().also { mediatorLivedata ->
var shouldDebounce = true
var job: Job? = null
val source = this
mediatorLivedata.addSource(source) {
if (shouldDebounce) {
job?.cancel()
job = CoroutineScope(coroutineContextProvider.IO).launch {
delay(duration)
withContext(coroutineContextProvider.Main) {
mediatorLivedata.value = source.value
shouldDebounce = false
}
}
} else {
job?.cancel()
mediatorLivedata.value = source.value
}
}
}
}
open class CoroutineContextProvider #Inject constructor() {
open val Main: CoroutineContext by lazy { Dispatchers.Main }
open val IO: CoroutineContext by lazy { Dispatchers.Default }
}
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())
})
I'm testing a view model which has the following definition:
class PostViewModel(private val postApi: PostApi): ViewModel() {
private val _post: PublishSubject<Post> = PublishSubject.create()
val postAuthor: Observable<String> = _post.map { it.author }
fun refresh(): Completable {
return postApi.getPost() // returns Single<Post>
.doOnSuccess {
_post.onNext(it)
}
.ignoreElement()
}
}
}
My fragment then displays the post author by subscribing to viewModel.postAuthor in its onActivityCreated and calling and subscribing to refresh() whenever the user wants an updated post and everything is fine and dandy.
The issue I'm running into is trying to verify this behaviour in a unit test: specifically, I am unable to get postAuthor to emit an event in my testing environment.
My test is defined as follows:
#Test
fun `When view model is successfully refreshed, display postAuthor`() {
val post = Post(...)
whenever(mockPostApi.getPost().thenReturn(Single.just(post))
viewModel.refresh()
.andThen(viewModel.postAuthor)
.test()
.assertValue { it == "George Orwell" }
}
The test fails due to no values or errors being emitted, even though I can verify through the debugger that the mock does in-fact return the Post as expected. Is there something obvious that I'm missing, or am I completely wrong in my testing approach?
viewModel.postAuthor is a hot-observable. It emits value when you call _post.onNext(it).
Unlike a cold-observable, the late subscribers cannot receive the values that got emitted before they subscribe.
So in your case I think the viewModel.postAuthor is subscribed after you call viewModel.refresh(), so it cannot receive the value.
The observable could be emitting on a different thread so that's why it's empty when the test is checking the values/errors.
You could try forcing your observable to emit on the same thread. Depending on which scheduler you're using, it'd be something like:
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
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'
}}