Closed ciprig closed 7 years ago
This implementation does not work with RxLifecycle
either and, and as Dan Lew pointed out:
I don't agree with how RxIdlingResource works: a subscription isn't an indicator of background activity! There are plenty of other cases, for example if you're using a subscription detect changes in an EditText, or a subscription for when a button is clicked, or..., or... that's the point of Rx.
Source: https://github.com/trello/RxLifecycle/issues/65
So I tried to tackle the problem from a different angle - instead of looking at the individual observables / subscriptions I look at the schedulers and came up with this:
public class RxSchedulersHook extends RxJavaSchedulersHook implements IdlingResource {
private static final String TAG = RxSchedulersHook.class.getSimpleName();
private static RxSchedulersHook sInstance;
private final AtomicInteger mScheduledActions = new AtomicInteger(0);
private ResourceCallback mResourceCallback;
private RxSchedulersHook() {
}
public static RxSchedulersHook get() {
if (sInstance == null) {
sInstance = new RxSchedulersHook();
Espresso.registerIdlingResources(sInstance);
}
return sInstance;
}
@Override
public Action0 onSchedule(final Action0 action) {
// This is a dirty hack: Since our implementation does not know from where it was scheduled, it also does not
// know whether or not our scheduled action will return immediately or block / recursively schedule itself again.
//
// The only way to kind-of getting this information is by looking at the current stack trace and check whether
// we have been scheduledPeriodically; in this case we skip schedule counting and just return the action undecorated.
if (scheduledPeriodically()) {
return action;
}
int current = mScheduledActions.incrementAndGet();
Timber.tag(TAG).d("scheduling action %s (%d)", action, current);
return () -> {
try {
action.call();
} finally {
int remaining = mScheduledActions.decrementAndGet();
Timbe.tag(TAG).d("action %s executed (%d)", action, remaining);
if (remaining == 0) {
mResourceCallback.onTransitionToIdle();
}
}
};
}
private boolean scheduledPeriodically() {
for (StackTraceElement el : Thread.currentThread().getStackTrace()) {
if (el.getMethodName().equals("schedulePeriodically")) {
return true;
}
}
return false;
}
@Override
public String getName() {
return TAG;
}
@Override
public boolean isIdleNow() {
return mScheduledActions.get() == 0;
}
@Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
mResourceCallback = resourceCallback;
}
}
Unfortunately this does not work well under all circumstances, e.g. I have found that ScheduledUnsubscribe
(a private inner worker of the public OperatorObserveOn
) might dead-lock itself somehow while it is unscheduling itself and as such prevents the IdlingResource
from getting idle. I'm still on it...
This is of course not ideal and is a current limitation of the library (as stated in the readme). I haven't had the time to dig in to find other (better) solutions to this but I do welcome contributions or alternative libraries that handle this better. This does however work quite well if your usage of RxJava is limited to one-shot async calls as we typically use them.
@tommyd3mdi Thx for sharing your try of tracking the actions. Have you found a solution for the ScheduledUnsubscribe dead-lock problem?
Now that we are manually counting by increment/decrement, this should work with RxBinding
RxBiding use hot observables which are never unsubscribe. A solution will be to only count for observables that don't execute on main thread, that might be complicated.