I perform a network call in which I
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
and then I pass the result to an io.reactivex.function.Consumer
, which attempts to animate a view.
These animations worked when I was loading the data locally and synchronously, but broke when I introduced this server call.
Now when I animate the views, it does not change any of the properties at all, but instead just instantly calls the animation complete listener.
I verified that I'm on the main thread with Looper.getMainLooper().isCurrentThread()
Yet the only way I've been able to get the animation working again is to wrap it in AndroidSchedulers.mainThread().scheduleDirect(() -> {})
Which as shown by this piece of code shouldn't actually change anything
final Thread before = Thread.currentThread();
AndroidSchedulers.mainThread().scheduleDirect(() -> {
boolean nothingChanged = before == Thread.currentThread();
//nothingChanged is true
});
Why do I have to reschedule the animation back onto the thread it is already on in order to get it to work?
Edit: RxJava is not involved, something even weirder is going on. I cut out RxJava and the issue is reproducing even though I put the animation code in onNavigationItemSelected(), which is called by android when the user clicks something, so its definitely in the right thread.
My animation looks like this.
alertBanner.clearAnimation();
alertBanner.animate()
.alpha(0)
.translationY(alertBanner.getHeight())
.setDuration(500)
.setListener(new AnimatorListenerBuilder().setOnAnimationEnd(animator -> {
alertBanner.setVisibility(GONE);
}))
.start();
AnimatorListenerBuilder is just a utility class that wraps AnimatorListener and lets you only define the callbacks that you want. It works for all the other animations in my app and hasn't been changed in months.
I figured it out. It was a race condition.
When the user clicked the button, two things happened:
A new fragment was created, and an animation was started.
However, the fragment had a callback, onAttach()
that would, among other things, call .clearAnimation()
on the view I was trying to animate.
By rescheduling the animation back onto the main thread, I was making sure that it happened after the fragment callback.
I solved it by getting rid of the callback from the fragment, and putting that logic in the same place as the logic that starts the animation, allowing me to control the execution order.