Android Espresso not synchronized with Rx Completable - android

I have an issue with Espresso not waiting for Completable to finish, so my UI test is Failing
apiDataSource.getData()
.flatMap { data ->
cacheDataSource.saveData(data)
.andThen(Observable.just(cacheDataSource.getData()))
is there a way to hold the Espresso thread until having the cacheDataSource.saveData(data) complete?
Thank you in advance

The reason is that you call cacheDataSource.getData() right when you assemble the save sequence inside flatMap. From the documentation of just:
Note that the item is taken and re-emitted as is and not computed by any means by just. Use fromCallable(Callable) to generate a single item on demand (when Observers subscribe to it).
apiDataSource.getData()
.flatMap { data ->
cacheDataSource.saveData(data)
.andThen(Observable.fromCallable { cacheDataSource.getData() })
}

Related

Where to put reused values used for every emission in Observable/ Flowable?

I have some expensive operations that only need to be performed once (e.g. load/ download large files, load large ML models, or calculate optimized data structure based on some other data). I want to use this for every value the Observable/ Flowable generates:
The following code works, but it runs heavyProcessing() and heavyProcessing2() on the caller's thread. In my case, I can't choose what my callers thread (its the main thread because I am using WorkManager's RxWorker, which calls createWork from main). Therefore, start blocks the main thread. How do I get heavyProcessing to be performed in the background with RxJava and also available to the subsequent RxJava chain?
fun start(): Observable<Unit> {
val heavy = heavyProcessing() // the heavy value i want to use everywhere!
val anotherHeavyObject = heavyProcessing2()
val items = Observable.fromIterable(listOfHundredsOfItems)
.map { doSomeWork(it, heavy) }
.map { doSomeWork(it, anotherHeavyObject) }
}
My attempts has so far not worked:
Create a wrapper around the existing function: The issue with this code is the Observable returned by start() does not get observed, so the doSomeWork doesn't actually get done. I only know this because I put breakpoints in at doSomeWork, and it never gets called.
fun startInBackground(): Single<Unit> {
return Single.fromCallable {
start()
}
}
I've been trying to find ways of 'unnesting' the inner Observable (inside the Single), as that's probably the issue here. The inner Observable is not being observed.
This RxJava stuff is very unintuitive even after reading the guide
Yes, it was related to Deferred-dependent. The example in the docs state:
Sometimes, there is an implicit data dependency between the previous sequence and the new sequence that, for some reason, was not flowing through the "regular channels". One would be inclined to write such continuations as follows:
AtomicInteger count = new AtomicInteger();
Observable.range(1, 10)
.doOnNext(ignored -> count.incrementAndGet())
.ignoreElements()
.andThen(Single.defer(() -> Single.just(count.get())))
.subscribe(System.out::println);
Actually, all I needed the caller to do is:
Single.defer { start() }.map { doMoreWork() }
instead of
start().map { doMoreWork() }

Kotlin Flow execute two API calls in parallel and collect each result as it arrives

I am trying to implement cache then network strategy for my API call using Kotlin Flows.
Here is what I am trying right now
flowOf(
remoteDataSource.getDataFromCache() // suspending function returning Flow<Data>
.catch { error -> Timber.e(error) },
remoteDataSource.getDataFromServer() // suspending function returning Flow<Data>
).flattenConcat().collect {
Timber.i("Response Received")
}
Problem here is collect is only called when getDataFromServer returns. My expectation is that I should get first event from cache and then second event from server after a few milliseconds. In this case "Response Received"gets printed twice but immediately one after other.
In this other variant "Response Received" only gets printed once that is after getDataFromServer() returns.
remoteDataSource.getDataFromCache() // suspending function returning Flow<Data>
.catch { error -> Timber.e(error) }
.flatMapConcat {
remoteDataSource.getDataFromServer() // suspending function returning Flow<Data>
}
.collect {
Timber.i("Response Received")
}
I was using RxJava's Flowable.concat() before and it was working perfectly. Is there something in Kotlin Flows which can emulate that behaviour?
Problem here is collect is only called when getDataFromServer returns.
The first problematic thing with your design is that the Flow-returning function is also suspendable. That's two layers of suspendability. Functions should return flows without any delays and the flows themselves should emit items as they come in. If you followed this guideline, your initial code would already work.
The way you wrote these functions, they can still work if you write this:
flow<String> {
emitAll(getCached())
emitAll(getFromServer())
}
This statement completes immediately, returning a cold flow. When you call collect on it, it first calls getCached() and emits the cached value, and then calls getFromServer() and emits the server response.
The above solution starts the server call only after you consume the cached value. If you need the two flows to be active concurrently, use flatMapMerge.
Assuming you fixed the above basic problem and made your Flow-returning functions non-suspending, all you need is this:
flowOf(getCached(), getFromServer()).flattenMerge()
If for some reason you can't do that, you have to add the emitAll wrapper around each call:
flowOf(
flow { emitAll(getCached()) },
flow { emitAll(getFromServer()) }
).flattenMerge()
Recently, merge operator was added to the Kotlin coroutines version 1.3.3. Here is the merged PR.
Using the merge operator, you should be able to get the result as and when it arrives.
Turns out in case of flowOf(someOperation()) someOperation() needs to be completed for downstream to start processing. Its like Observable.just(someOperation()) in RxJava world.
In second scenario flatMapConcat is actually a transform operator so it obviously returns final processed output.
There seems to be lack of native concat like operators in Flow world. This is how I solved this problem in the end
flow {
remoteDataSource.getDataFromCache()
.catch { error -> Timber.e(error) }
.onCompletion {
remoteDataSource.getDataFromServer()
.collect {
emit(it)
}
}.collect { emit(it) }
}

RxJava2 wrapper for Firebase Firestore without switching threads?

I'm trying to create a reactive observable for Firebase Firestore calls.
I'm facing a threading issue. I'm using rxjava2 to handle threads and I don't want Firestore API to do that for me. It seems like Firestore calls are async, thus OnSuccess method is getting called on the main thread
Here is a simple example that showcases the issue:
Single<Integer> firestoreSingle = Single.create(emitter -> {
Log.d("TAG", Thread.currentThread().getName()); // -> RxCachedThreadScheduler-3 Thread
CollectionReference collectionRef = FirebaseFirestore.getInstance().collection("test_collection");
collectionRef.get().addOnSuccessListener(queryDocumentSnapshots -> {
Log.d("TAG",Thread.currentThread().getName()); // -> MAIN THREAD
List<DocumentSnapshot> documentSnapshotList = queryDocumentSnapshots.getDocuments();
emitter.onSuccess(documentSnapshotList.size());
}).addOnFailureListener(emitter::onError);
});
firestoreSingle
.subscribeOn(Schedulers.io())
.subscribe(howManyDocs -> {
Log.d("TAG",Thread.currentThread().getName()); // -> MAIN THREAD
Log.d("TAG","How many docs: " + howManyDocs);
});
Of course, I could add .observeOn(Schedulers.io()) to the reactive stream, but then I would not necessarily get the results on the same thread as the one I initially subscribed on.
I don't want the results neither in the main thread, nor in a different thread that the one I subscribed on.
Is there a way to query Firestore synchronously? How would you solve this issue?
You can use the answer that #MarkKeen suggested in the comment but for reference if you want to stick with RxJava, you can always call the method .blockingGet() to, as it suggests, block until a value is emitted.

Observable startWith isn't emitted in doOnNext

I have a number of Observables that are used for network requests in my app. Since so much is the same, I apply an Observable transformation to them:
/**
* Creates a transformer that applies the schedulers and error handling for all of the observables in this ViewModel.
*/
private fun applyTransformations(): Observable.Transformer<NetworkState, NetworkState> {
return Observable.Transformer { observable ->
observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorReturn { NetworkState.Error(it) }
.doOnNext { publishState(it) }
.startWith(NetworkState.Loading())
}
}
The goals I am trying to accomplish with the above:
Apply consistent schedulers
Handle any error by returning an instance of my sealed class.
Handle any onNext by publishing the state returned by the observable.
Start off by sending a Loading state.
This works mostly fine, but what I've noticed is that while I call startWith and a loading state, it is never actually handled by doOnNext(). In other words, publishState() is never called for my loading state.
Where I set up the observables, I don't bother to add a subscriber, because the doOnNext() above is all that I'll need:
val subscription = repository.getInstagramPhotos(count)
.map { mapIGPhotoResponse(it) }
.compose(applyTransformations())
.subscribe()
If I were to supply a subscriber above, though, it would handle the loading state. It would also handle two onNext() calls - one for the subscriber supplied, and one for the doOnNext in the transform.
Is there a way to modify this startWith call to emit to whatever I've specified in doOnNext? I'm using RxJava 1.
Edit: Just to clarify some more, if I track what's emitted I expect to see two things. Loading -> Success. What I actually see is just Success. If I supply a subscriber to the observable I see Loading -> Success -> Success.
startWith should be before doOnNext.
Rxjava methods, though they look like they use the builder pattern, actually don't. They return a new observable each time an operator is applied. In your case, your doOnNext observable completes before your start with observable, so it's consumer isn't called with what you supply in startWith.
Ideally, you should go with:
observable
.startWith(NetworkState.Loading())
.doOnNext { publishState(it) }
.onErrorReturn { NetworkState.Error(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
Also, be careful with subscribing with no Consumer for onEror should it happen. Since you have nothing to consume the onError, RxJava will crash your app since it has nothing to notify for the error. Consider replacing the doOnNext with a Success Consumer in subscribe, and an empty Consumer for the error if you want to ignore it.
Also doOnNext is typically used for side effects, such as logging and the sort, they're more of a convenience than true functional operators.

RxView.clicks() dies after onError event

Here is a sample Rx chain using RxBindings:
RxView.clicks(refreshIcon)
.flatMap { Observable.error<Throwable>(IllegalArgumentException()) }
.subscribe(
{ Timber.d("onNext")},
{ error -> Timber.d("onError") })
.addTo(disposables)
After clicking my refresh icon once, this chain will not run again as a terminal event took place. I am guessing I was under the wrong impression that the subscription takes place whenever a click event is detected, and that it in fact subscribes whenever that block of code gets executed.
Question is how can I make my chain execute/re-execute on every click, even after it hits a terminal event? Looking for something intuitive.
Observable must complete when the first error occur, it's in their contract. In order to have your Observable survive terminal event, you will have to dig in RxJava Error handling operators. retry() seems a good fit in your case:
RxView.clicks(refreshIcon)
.flatMap { ... }
.retry()
.subscribe(...)
.addTo(disposables)
It is part of the Rx contract when an error occurred the stream will receive a onError event and will terminate.
Unless you actively handle the error, using for example: onErrorResumeNext()

Categories

Resources