Reputation: 22988
In an Android app allowing user logins via social networks I show and hide a FAB
using the following code:
public abstract class LoginFragment extends Fragment {
private FloatingActionButton mFab;
private Animation mShowFab;
private Animation mHideFab;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mShowFab = AnimationUtils.makeInAnimation(getContext(), false);
mShowFab.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mFab.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
mHideFab = AnimationUtils.makeOutAnimation(getContext(), true);
mHideFab.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mFab.setVisibility(View.INVISIBLE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
private void showFab(boolean show) {
boolean visible = mFab.isShown();
if (show && !visible) {
mFab.startAnimation(mShowFab);
} else if (!show && visible) {
mFab.startAnimation(mHideFab);
}
}
This works well, when I call the above showFab
method slow enough.
Before starting any animation I check for current FloatingActionButton
visibility, so that the animation is played only once - even if I call for example showFab(true)
several times in a row.
My problem:
When a LoginFragment
is shown in my app, I first send a request to a ServiceIntent
to fetch user data from SQLite and call the following method to set my UI to a "waiting" state:
private void setBusy(boolean busy) {
mProgressBar.setVisibility(busy ? View.VISIBLE : View.INVISIBLE);
showFab(!busy);
}
Almost immediately a response from SQLite comes back - via a LocalBroadcastManager
and I call the above method again: setBusy(false)
.
And then the error occurs and the FAB
is not visible.
If I replace the FAB
method by animation-less code everything works fine:
private void showFab(boolean show) {
mFab.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
But with animation - a racing condition seems to occur.
As a workaround I have tried canceling both animations - but this does not help:
private void showFab(boolean show) {
mShowFab.cancel();
mShowFab.reset();
mHideFab.cancel();
mHideFab.reset();
boolean visible = mFab.isShown();
if (show && !visible) {
mFab.startAnimation(mShowFab);
} else if (!show && visible) {
mFab.startAnimation(mHideFab);
}
}
Please suggest what could be done here.
I have stepped through my app in debugger numerous times already. The setBusy
(and showFab
) are called only twice when the Fragment is shown, but both calls happen very quickly - and the FAB
is not shown -
First run:
Second run:
UPDATE:
Unfortunately, making the method synchronized
does not help either - the FAB
stays hidden:
private synchronized void showFab(boolean show) {
mShowFab.cancel();
mShowFab.reset();
mHideFab.cancel();
mHideFab.reset();
boolean visible = mFab.isShown();
if (show && !visible) {
mFab.startAnimation(mShowFab);
} else if (!show && visible) {
mFab.startAnimation(mHideFab);
}
}
Upvotes: 6
Views: 1108
Reputation: 22988
Here is my own solution -
First, using synchronized
does not help here, because the animations run in the same thread.
My solution has been to introduce a separate boolean
variable to hold the intended final FAB state (visible or not visible):
private FloatingActionButton mFab;
private boolean mFabVisible;
private Animation mShowFab;
private Animation mHideFab;
private void showFab(boolean show) {
if (show && !mFabVisible) {
mFab.startAnimation(mShowFab);
} else if (!show && mFabVisible) {
mFab.startAnimation(mHideFab);
}
mFabVisible = show;
}
Also, I think another possibility might have been to call mFab.setVisibility(View.VISIBLE)
twice - as in the below code. But I haven't really tested it:
mShowFab = AnimationUtils.makeInAnimation(getContext(), false);
mShowFab.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mFab.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animation animation) {
mFab.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
mHideFab = AnimationUtils.makeOutAnimation(getContext(), true);
mHideFab.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mFab.setVisibility(View.INVISIBLE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
Upvotes: 0
Reputation: 1076
Each of your animations run in separate threads. This leads to a race condition as you said.
Here's whats going on: showFab is fired with true, then false.
The variables in your second run show that the mShowFab animation will never run.
You should be able to resolve the race condition by listening for the end of the hide animation. Something like this:
private void showFab(boolean show) {
if (show) {
// if you have an animation currently running and you want to show the fab
if (mFab.getAnimation() != null && !mFab.getAnimation().hasEnded()) {
// then wait for it to complete and begin the next one
mFab.getAnimation().setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mFab.setVisibility(View.INVISIBLE);
mFab.startAnimation(mShowFab);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
} else {
mFab.startAnimation(mShowFab);
}
} else {
mFab.startAnimation(mHideFab);
}
}
Upvotes: 3
Reputation: 86
The first thing to point out is that there is nothing like racing condition. The right terminology is just race condition. Keep this in mind for the future ;-)
I suppose you are using a BroadcastReceiver to receive messages and that you have created the object and tell it to run in a separate thread because of the animation. This means that your showFab method can be called twice in one time. To handle this, define the method as synchronized.
Upvotes: 1