Android Espresso is waiting rxjava .delay from observable - android

I have a server call made with Retrofit + RxJava that i want to test its behaviour on screen.
The goal is to have a loading image set before the call be performed and, after getting the results, hide the loading image and show the data.
I tried to setup the mock using the "delay" method from the Observable class, so Espresso can find the image. That's the code i used:
Observable<AccountDetails> observable = Observable.just(details)
.delay(5, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io());
doReturn(observable).when(mScope).requestAccounts();
performServerCall();
onView(withId(R.id.panel_loading)).check(matches(isDisplayed()));
After running the test, i realized that the Espresso is actually waiting the delay set on Observable, before actually executing the check (isDisplayed). That way it will only check after the info is loaded and the loading image is gone.
Is that the normal behaviour for RxJava/Espresso?
Is there a better way achieve this?

There must be an animation in the R.id.panel_loading that is being executed.
When there is an animation in the UI thread espresso waits until it is finished.
I had the same problem and I did a ViewAction to disable the animations for my custom loadings, here is the code:
public static ViewAction disableAnimations() {
return new ViewAction() {
#Override
public Matcher<View> getConstraints() {
return isAssignableFrom(CustomLoading.class);
}
#Override
public String getDescription() {
return "Disable animations";
}
#Override
public void perform(UiController uiController, View view) {
CustomLoading loading = (CustomLoading) view;
loading.setAnimations(false);
}
};
}
and I call it this way before pressing the button that shows the loading and so the tests do not wait:
onView(withId(R.id.panel_loading)).perform(disableAnimations());
If the panel_loading is not the one that makes the animation something else must be.
Hope this helps.

Related

RxJava as event bus is called multiple times even when only once triggered

I am trying to implement the RxJava event bus, where my use case is to get triggered when subscribed and when the event is sent. But with my code even when I send the event once, I am receiving multiple events. It is working fine for the first time, it is behaving weirdly from the second time I login into my application. i.e; For the first time desired code implemented once, for the second time it implemented two time and so on.
public class RxBus {
public RxBus() {
}
private PublishSubject<String> bus = PublishSubject.create();
public void send(String str) {
bus.onNext(str);
}
public Observable<String> toObservable() {
return bus;
}
}
The code to subscribe RxBus is below:
public void sendEvents(){
rxBus.send("Trigger event");
}
public void startListener(){
rxBus.toObservable().subscribe(str -> {
//do action//This is executing multiple lines
});
}
In the above code, even though when the sendEvents() is executed once the line containing "do action" is executing multiple times. So, is something I am doing wrong here. When I went through some blogs they are asking to unsubscribe the subscription when we visit that screen a second time. But how can I unsubscribe from that?
Help here is greatly appreciated!
Easy solution is to declare a field:
SerialDisposable busDisposable = new SerialDisposable();
Modify you startListener method:
public void startListener() {
busDisposable.set(rxBus.toObservable().subscribe(str -> {
// ...
}));
}
In that way, when you add new subscription the previous one will be disposed, so you will end up with only one subcription at a time. This is good if your startListener call is not determined by the lifecycle. (Remember to call busDisposable.dispose() when you no longer want to recieve events. )
But if you call your startListener in onResume/onStart/onCreate, you should better use Disposable instead of SerialDisposable and simply call stopListener method in onPause/onStop/onDestroy.
public void stopListener() {
busDisposable.dispose();
}

Leaked Retrofit call causes UI to go blank

I use Retrofit and RxJava for network calls. For the first time I ran into a weird problem. For one of the calls the following error message is displayed:
HTTP FAILED: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
But the call is started from the main thread and it's the same result even if I remove any calls in onNext. So it must be something in the call, which is a solo call! This is the call in a presenter:
public void updateEmail(final String newEmail) {
disposables.add(AccountRepository.updateEmail(newEmail)
.retry(1)
.subscribeWith(new DisposableObserver<Reply>() {
#Override
public void onNext(Reply reply) {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
}));
}
And this is the Repository call:
public static Observable<Reply> updateEmail(String email) {
return getMyApiService().updateEmail(email)
.subscribeOn(Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR))
.observeOn(AndroidSchedulers.mainThread());
}
And the Retrofit interface called MyApiInterface:
#FormUrlEncoded
#POST(UrlMap.apiProfileUpdateEmailUrl)
Observable<Reply> updateEmail(#Field("email") String email);
Nothing unusual, I have more than 100 calls like this.
Now if I add retry(1) as above, the call goes through, but the first call seem to be leaked, because it's still in the threads in the Network Profiler.
Furthermore, The call happens in a Fragment in an Activity with bottom navigation. If I switch to another fragment, all the calls there finish in the background without error, but the UI is not updated, I see a blank screen. But if I navigate to another activity and back, the UI is updating again.
The disposables is a CompositeDisposable object and it's cleared when the presenter is unbound. Disposable.clear in onNext() doesn't help.
I added five interceptors to the client for logging, headers, etc. Maybe they cause this somehow? Or something else? I'm trying to fix this for more than a day, but couldn't get much closer to the solution.
There is a listener for user verification changes -- that are coming from all the response headers -- in the main feed, so we can obfuscate the images. I wrapped the listener in a runOnUiThread() method and the leak went away.

RxJava pattern for returning cold results, doing more work, then returning hot results

I'm learning RxJava so please be gentle. I've watched the tutorials, done the reading, searched SO, however, I'm still having some problems transforming my AsyncTaskLoader. For some reason, I can't find a pattern of operators to achieve my task (although I think it's a common one). What I'm trying to do is the following: return an Observable my fragment could subscribe to. The observable should do the following on subscribe:
1) Fetch data from the local database by doing 2 queries, running some logic and returning results;
2) Fetching data from API;
3) Synchronising the new API data with the database;
4) Repeating step one and returning results;
So far I've transformed my db calls and my API calls to return observables. I'm trying to understand how I can emit the cold results and continue with the chain. I could probably keep the two operations separately, and use the same subscriber to subscribe to both? But I'm not sure how that would work if my new loader-replacement class returns an observable... Also I don't really need to process the results from the second observable - I just need for the first one to replay when the second one finished.
So far I have the following:
public Observable<StuffFetchResult> getColdStuff() {
return Observable.zip(mDataSource.listStuff(), mDataSource.listOtherStuff(),
(stuff, moreStuff) -> {
List<Stuff> mergedList = new ArrayList<>();
// do some merging stuff
return new StuffFetchResult(mergedList);
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
Assume I also have getHotStuff() that will do the API call and the synchronisation with the database, if that's the right approach, and return the same Observable. However, I'm stuck on the next step - how can I restart the first observable to replay once hotStuff has completed, without adding another subscriber?
EDIT:
I've made some progress and I think all I need now is to join it all up. I have my two methods:
1) getColdStuff() is pretty much as described above
2) getHotStuff() will do call to the API, synchronise with the database, and return an Observable. The idea was to call getColdStuff() again after getHotStuff() has finished in order to refresh the UI, so actual result returned from getHotStuff() can be ignored. All it needs to do is to trigger getColdStuff() once done.
I've tried the suggestion in the answer to and created the following:
BehaviorRelay<Observable<StuffFetchResult>> callSequence = BehaviorRelay.create();
Observable<StuffFetchResult> valueSequence = Observable.switchOnNextDelayError(callSequence.toSerialized());
valueSequence.subscribe(new Subscriber<StuffFetchResult>() {
#Override
public void onCompleted() {}
#Override
public void onError(Throwable e) {}
#Override
public void onNext(StuffFetchResult result) {
// UI stuff
}
});
callSequence.call(loader.getColdStuff());
I can subscribe to valueSequence here and use callSequence.call(loader.getColdStuff());, which will run the first method and produce results in onNext() of my subscription, which I can use for my UI. However, I'm not sure how to run getHotStuff() in parallel and also do a different action on it when it returns. Also getHotStuff() returns a different type of Observable so I can't really use the same callSequence?
EDIT 2
Using two subscribers, I can achieve the required behaviour I think. Not really sure if that's the right way to go about it though.
loader.getHotStuff()
.subscribeOn(Schedulers.io())
.subscribe( new Subscriber<Object>() {
#Override
public void onCompleted() {}
#Override
public void onError(Throwable e) {}
#Override
public void onNext(Object stuffWeDontCareAbout) {
callSequence.call(loader.getColdStuff());
}
});
if i understand your scenario correctly, you may want something like that -
BehaviorSubject<Observable<T> callSequence = BehaviorSubject.create();
Observable<T> valueSequence = Observable.swithOnNextDelayError(callSequence.toSerialized());
your subscriber will be listening to the valueSequence, and whenever you need to "restart", you will call this -
callSequence.onNext(call.cache()); // *call* is Observable<T>
(i leave the .subscribeOn/.observeOn configuration to you)

Debouncing button clicks using Rx

I'm trying to make a simple "button debouncer" which will count filtered clicks and display it thru a TextView. I want to filter rapid/spam clicks in a way that clicks with less than 300ms time-gap in-between are ignored.
I did my research and stumbled upon Rx's awesome debounce() which in theory should do the exact thing I wanted..
..or so I thought. As the app seemed to only register the first click; the counter won't increment no matter how long I tried to wait.
Here's a piece of my code:
...
RxView.clicks(mButton)
.debounce(300, TimeUnit.MILLISECONDS)
.subscribe(new Subscriber<Object>() {
public int mCount;
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Object o) {
mText.setText(String.valueOf(++mCount));
}
});
...
What am I doing wrong? I've tried to run the thing without debounce() and it worked flawlessly (the counter will increment everytime the button got clicked).
Thanks in advance!
Note the following in the documentation on the debounce operator:
This variant operates by default on the computation Scheduler (...)
Or, code-wise, this currently happens:
public final Observable<T> debounce(long timeout, TimeUnit unit) {
return debounce(timeout, unit, Schedulers.computation());
}
As a result, the subscriber's callbacks are invoked on that same computation scheduler, since nothing is explicitly instructing otherwise.
Now, attempting to update a view (that's what's happening in onNext()) from any other thread than the main/ui thread, is a mistake and it will lead to undetermined results.
Fortunately, the remainder of the quote above provides the solution too:
(...) but you can optionally pass in a Scheduler of your choosing as a third parameter.
This would lead to:
RxView.clicks(mButton)
.debounce(300, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe(...);
Alternatively, you can still let the debounce happen on the computation scheduler, but receive the notifications on the main/ui thread:
RxView.clicks(mButton)
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);
Either way will ensure that the notifications are received on the main/ui thread and thus that the view is updated from the correct thread.

Espresso and postDelayed

I have an activity which is using a postDelayed call:
public class SplashActivity extends Activity {
private Handler handler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(...);
handler.postDelayed(new Runnable() {
public void run() { finish(); }
}, 3000L);
}
}
This runs at app startup, and i need to navigate it and my login screen. However, the UIController's loopMainThreadUntilIdle doesn't seem to take the underlying MessageQueue in the handler into account. As such, this action finishes immediately while there is still messages in the queue.
onView(withId(R.id.splash_screen)).perform(new ViewAction() {
#Override
public Matcher<View> getConstraints() {
return isAssignableFrom(View.class);
}
#Override
public String getDescription() {
return "";
}
#Override
public void perform(final UiController uiController, final View view) {
uiController.loopMainThreadUntilIdle();
}
});
I've been unable to figure out how to block until the queue is drained. Android itself is preventing me from doing a lot of things i would have tried (like extending Handler and overriding the postDelayed method, etc...)
Anyone have any suggestions on how to handle postDelayed?
I'd rather avoid uiController.loopMainThreadForAtLeast, which seems hacky (like a Thread.sleep would)
When Espresso waits, it actually does take in account MessageQueue, but in a different way from what you think. To be idle, the queue must either be empty, or have tasks to be run in more than 15 milliseconds from now.
You can check the code yourself, especially the method loopUntil() in UiControllerImpl.java and the file QueueInterrogator.java. In the latter file you will also find the logic of how Espresso checks the MessageQueue (method determineQueueState()).
Now, how to solve your problem? There are many ways:
Use AsyncTask instead of Handler, sleeping on the background thread and executing actions onPostExecute(). This does the trick because Espresso will wait for AsyncTask to finish, but you might not like the overhead of another thread.
Sleep in your test code, but you don't like that approach already.
Write your custom IdlingResource: this is a general mechanism to let Espresso know when something is idle so that it can run actions and assertions. For this approach you could:
Use the class CountingIdlingResource that comes with Espresso
Call increment() when you post your runnable and decrement() inside the runnable after your logic has run
Register your IdlingResource in the test setup and unregister it in the tear down
See also: docs and sample, another sample
As far as I know there is no wait for activity to finish method in espresso. You could implement your own version of waitForCondition, something robotium has. That way you'll only wait for as long as is needed and you can detect issues with your activity not finishing.
You'd basically poll your condition every x ms, something like.
while (!conditionIsMet() && currentTime < timeOut){
sleep(100);
}
boolean conditionIsMet() {
return "espresso check for if your splash view exists";
}

Categories

Resources