So basically what I'm working on is very similar to Instagram application, where there're a number of tabs and users can switch to any tab without any delay no matter what there's anything going on, such as refreshing, reloading, and etc. It also uses back button to go back to the previous preserved tab.
In order to achieve this, I've used FragmentManager
with FragmentTransaction
to show and hide each fragment which represents each tab. I didn't use replace
or attach
/ detach
because they destroy view hierarchy of previous tab.
My implementation works pretty well except that showing and hiding fragments are not committed (I highly doubt that this is a right word to say but so far that's how I understood the flow.), or don't occur immediately when SwipeRefreshLayout
is refreshing on the fragment (to be hidden) which was added to FragmentManager
later than the one to show.
My implementation follows the rules like these. Let's say we have 4 tabs and my MainActivity is showing the first tab, say FirstFragment, and the user selects the second tab, SecondFragment. Because SecondFragment had never been added before, I add it to FragmentManager
by using FragmentTransaction.add
and hide FirstFragment by using FragmentTransaction.hide
. If the user selects the first tab again, because FirstFragment was previously added to FragmentManager
, it doesn't add but only show FirstFragment and just hide SecondFragment. And selecting between these two tabs works smoothly.
But when the user "refreshes" SecondFragment's SwipeRefreshLayout
and selects the first tab, FragmentTransaction
waits for SecondFragment's refresh to be finished and commits(?) the actual transaction. The strange thing is that the transaction is committed immediately the other way around, from FirstFragment's refresh to SecondFragment.
Because this occurs by the order of addition to FragmentManager
, I doubt that the order of addition somehow affects backstack of fragments and there might exists something like UI thread priority so that it forces the fragment transaction to be taken place after later-added fragment's UI transition finishes. But I just don't have enough clues to solve the issue. I've tried attach / detach and backstack thing on FragmentTransaction
but couldn't solve the issue. I've tried both FragmentTransaction.commit
and FragmentTransaction.commitAllowingStateLoss
but neither solved the issue.
These are my MainActivity's sample code.
private ArrayList<Integer> mFragmentsStack; // This is simple psuedo-stack which only stores
// the order of fragments stack to collaborate
// when back button is pressed.
private ArrayList<Fragment> mFragmentsList;
@Override
protected void onCreate() {
mFragmentsStack = new ArrayList<>();
mFragmentsList = new ArrayList<>();
mFragmentsList.add(FirstFragment.newInstance());
mFragmentsList.add(SecondFragment.newInstance());
mFragmentsList.add(ThirdFragment.newInstance());
mFragmentsList.add(FourthFragment.newInstance());
mMainTab = (MainTab) findViewById(R.id.main_tab);
mMainTab.setOnMainTabClickListener(this);
int currentTab = DEFAULT_TAB;
mFragmentsStack.add(currentTab);
getSupportFragmentManager().beginTransaction().add(R.id.main_frame_layout,
mFragmentsList.get(currentTab), String.valueOf(currentTab)).commit();
mMainTab.setCurrentTab(currentTab);
}
// This is custom interface.
@Override
public void onTabClick(int oldPosition, int newPosition) {
if (oldPosition != newPosition) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
// First hide the old tab.
fragmentTransaction.hide(mFragmentsList.get(oldPosition));
// Recalculate the fragment stack.
if (mFragmentsStack.contains(newPosition)) {
mFragmentsStack.remove((Integer) newPosition);
}
mFragmentsStack.add(newPosition);
// Add new fragment if it's not added before, or show new fragment which was already hidden.
Fragment fragment = getSupportFragmentManager().findFragmentByTag(String.valueOf(newPosition));
if (fragment != null) {
fragmentTransaction.show(fragment);
} else {
fragmentTransaction.add(R.id.main_frame_layout, mFragmentsList.get(newPosition),
String.valueOf(newPosition));
}
// Commit the transaction.
fragmentTransaction.commitAllowingStateLoss();
}
}
// It mimics the tab behavior of Instagram Android application.
@Override
public void onBackPressed() {
// If there's only one fragment on stack, super.onBackPressed.
// If it's not, then hide the current fragment and show the previous fragment.
int lastIndexOfFragmentsStack = mFragmentsStack.size() - 1;
if (lastIndexOfFragmentsStack - 1 >= 0) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.hide(mFragmentsList.get(mFragmentsStack.get(lastIndexOfFragmentsStack)));
fragmentTransaction.show(mFragmentsList.get(mFragmentsStack.get(lastIndexOfFragmentsStack - 1)));
fragmentTransaction.commitAllowingStateLoss();
mMainTab.setCurrentTab(mFragmentsStack.get(lastIndexOfFragmentsStack - 1));
mFragmentsStack.remove(lastIndexOfFragmentsStack);
} else {
super.onBackPressed();
}
}