I have Activity with five fragments which switch by pressing button in BottomNavigationView:
bottomNavigationView.setOnNavigationItemSelectedListener(item -> {
switch (item.getItemId()) {
case R.id.first:
replaceFragment(FIRST_TAG);
return true;
case R.id.other:
// last four fragments
default:
return false;
}
});
in replaceFragment:
private void replaceFragment(#NonNull String tag) {
if (!checkFragment(tag)) {
addFragment(getFragmentByTag(tag), tag);
}
showFragment(tag);
}
сheckFragment:
private boolean checkFragment(String tag) {
return fm.findFragmentByTag(tag) != null;
}
addFragment:
private void addFragment(Fragment fragment, String tag) {
if (!checkFragment(tag)) {
fm.beginTransaction().add(R.id.frame, fragment, tag).hide(fragment).commit();
}
}
showFragment:
private void showFragment(String tag) {
// currentFragmentTag defines as FIRST_TAG in fields block
currentFragment = fm.findFragmentByTag(currentFragmentTag);
FragmentTransaction transaction = fm.beginTransaction();
if (currentFragment != null) {
transaction.hide(currentFragment);
}
transaction
.attach(getFragmentByTag(tag))
.show(getFragmentByTag(tag))
.commit();
currentFragmentTag = tag;
}
This was done to avoid load data in fragments every time when fragment is shown! All fragments added to FragmentManager only once and later there are only calls hide(previousFragment) and show(nextFragment).
This works well!
Before app goes to background and new heavy app is launched. In background activity of my app is destroyed and when I return to app there is only BottomNavigationView and empty display.
I checked in debug. And FragmentManaget.getActiveFragments() returns list of five fragments.
So, Activity is restored, FragmentManager is restored, FragmentManager contains fragment.
Why fragments not shown on display?
After skimming through your code what I can think of, is to set one of the five fragments as a default fragment to be displayed when the app runs.
Try something like:
bottomNavigationView.getMenu().findItem(R.id.first).setChecked(true);
Do this inside onCreate() or onResume();
Good luck!
Related
I'm using a bottom navigation view with four fragments, to avoid recreation of fragments I'm using this code snippet:
private void changeFragment(Fragment fragment, String tagFragmentName) {
FragmentTransaction ft = fm.beginTransaction();
Fragment currentFragment = fm.getPrimaryNavigationFragment();
if (currentFragment != null) {
ft.hide(currentFragment);
}
Fragment fragmentTemp = fm.findFragmentByTag(tagFragmentName);
if (fragmentTemp == null) {
fragmentTemp = fragment;
ft.add(R.id.content, fragmentTemp, tagFragmentName);
} else {
ft.show(fragmentTemp);
}
ft.setPrimaryNavigationFragment(fragmentTemp).commit();
}
private void showProspectFragment() {
changeFragment(ProspectContainerFragment.newInstance(), ProspectContainerFragment.class.getSimpleName());
}
private void showChatsFragment() {
changeFragment(ChatsFragment.newInstance(), ChatsFragment.class.getSimpleName());
}
....
// Bottom nav item click listener
binding.bottomNav.setOnNavigationItemSelectedListener(item -> {
switch (item.getItemId()) {
case R.id.prospect:
showProspectFragment();
return true;
case R.id.chat:
showChatsFragment();
break;
...
}
});
But whenever activity is recreated (using recreate()), for some reason fragments are not visible, no matter how many times I tap on bottom nav.
Are you using show/hide functionality? If so,
I think you must call one function in class onCreate method.
I have an activity which hosts three Fragment's and I can switch between Fragment's using BottomNavigationView.The mechanism that I use to switch between Fragment's is using show and hide functions of FragmentTransaction instead of replace function of FragmentTransaction. I am doing so because I want some network operations to be done only once and also to inflate Layout only once.
The problem that I am facing using this mechanism is that when I start another Activity from any of the Fragment and then hit the back button the selectedItem of the BottomNavigationView and the Fragment shown are mismatching.
I was able to solve this problem though but I feel it has less efficiency. The procedure was that whenever I clicked a tab in BottomNavigation while switching Fragment's I gave it some predecided number and saved in a static variable(X) and whenever I clicked back button in the OnResume() method of the hosting activity I made a switch-case block using X to know which Fragment was visible before starting the new Activity and then finally making three FragmentTransaction's to show and hide required Fragment's.
protected void onResume() {
super.onResume();
if(selectedId!=63){
switch(selectedId){
case 0:if(bottomNavigationView.getSelectedItemId()==R.id.navigation_home){handleHomeFragmentVisibility();}
break;
case 1:if(bottomNavigationView.getSelectedItemId()==R.id.navigation_dashboard)
{handleDashboardFragmentVisibility();}
break;
case 2:if(bottomNavigationView.getSelectedItemId()==R.id.navigation_notifications)
{handleNotificationFragmentVisibility();}
break;
}
}
I feel using three FragmentTransaction's is costly and I was looking for some efficient way. Can you tell me one if you know ?
public void handleHomeFragmentVisibility(){
FragmentManager fragmentManager= getSupportFragmentManager();
if (fragmentManager.findFragmentByTag("home") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("home")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
Log.e(TAG,"homeFragmentAdded");
fragmentManager.beginTransaction().add(R.id.container, new HomeFragment(), "home").commit();
}
if (fragmentManager.findFragmentByTag("dashboard") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("dashboard")).commit();
}
if (fragmentManager.findFragmentByTag("requests") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("requests")).commit();
}
}
public void handleDashboardFragmentVisibility(){
FragmentManager fragmentManager= getSupportFragmentManager();
if (fragmentManager.findFragmentByTag("dashboard") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("dashboard")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
fragmentManager.beginTransaction().add(R.id.container, new DashboardFragment(), "dashboard").commit();
}
if (fragmentManager.findFragmentByTag("home") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("home")).commit();
}
if (fragmentManager.findFragmentByTag("requests") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("requests")).commit();
}
}
public void handleNotificationFragmentVisibility(){
FragmentManager fragmentManager= getSupportFragmentManager();
if (fragmentManager.findFragmentByTag("requests") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("requests")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
fragmentManager.beginTransaction().add(R.id.container, new NotificationFragment(), "requests").commit();
}
if (fragmentManager.findFragmentByTag("home") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("home")).commit();
}
if (fragmentManager.findFragmentByTag("dashboard") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("dashboard")).commit();
}
}
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
FragmentManager fragmentManager = getSupportFragmentManager();
switch (item.getItemId()) {
case R.id.navigation_home:
selectedId=0;
handleHomeFragmentVisibility();
break;
case R.id.navigation_dashboard:
selectedId=1;
handleDashboardFragmentVisibility();
break;
case R.id.navigation_notifications:
selectedId=2;
handleNotificationFragmentVisibility();
break;
}
return true;
}
});
A first note on your code: Avoid boilerplate! Write only one method instead of three and use a signature of the type handleFragmentVisibility(String show, String hide1, String hide2, int container). In case the fragment to be shown is null, instantiate it by testing for show, something like:
Fragment newFragment = (show == "home") ? new HomeFragment() : (show == "dashboard") ? new DashboardFragment() : new NotificationFragment();
However, none of your fragments should ever get null through hiding (please check for yourself), since you don't remove them from your activity or replace them with other fragments. Instead of using show and hide you could also use attach and detach, both sets of methods keep state. I don't see an efficiency problem and you do indeed need to call three FragmentTransactions. It only can be done with less code:
public void handleFragmentVisibility(String show, String hide1, String hide2){
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag(show)).commit();
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag(hide1)).commit();
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag(hide2)).commit();
}
Please note, that although this method keeps the state of the fragment while hiding or detaching them, other events like orientation change still make it necessary that you take care of saving state in onSaveInstanceState(Bundle savedInstanceState).
I have an Android activity that holds and manages six fragments, is fragment is a step in a flow, some of the fragments are replaced and some of them are added.
The Activity just uses a Framelayout as the container for the fragments as follows:
<FrameLayout
android:id="#+id/content"
android:layout_below="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Then the flow of the fragments is like this:
//Activity starts, add first Fragment
fragmentManager.beginTransaction().replace(R.id.content, FirstFragment.newInstance(listOfItems)).commit();
then
//User pressed button, activity got callback from first fragment
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.content, fragment2);
transaction.addToBackStack("frag2");
transaction.commit();
then
//Another callback from Frag2, perform the add of frag 3
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.content, fragment3);
transaction.addToBackStack("frag3");
transaction.commit();
And so on....
I also manage the back stack from the Activity like this:
//Controlling the back stack when the user selects the soft back button in the toolbar
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (fragmentManager.getBackStackEntryCount() == 0) {
super.onBackPressed();
overridePendingTransition(R.anim.no_change, R.anim.slide_down);
} else {
if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
super.onBackPressed();
Fragment fragment = fragmentManager.getFragments()
.get(fragmentManager.getBackStackEntryCount());
fragment.onResume(); //Make sure the fragment that is currently at the top of the stack calls its onResume method
}
}
return true;
}
return super.onOptionsItemSelected(item);
}
//Controlling the back stack when the user selects the "hardware" back button
#Override
public void onBackPressed() {
if (fragmentManager.getBackStackEntryCount() == 0) {
super.onBackPressed();
overridePendingTransition(R.anim.no_change, R.anim.slide_down);
} else {
if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
super.onBackPressed();
Fragment fragment = fragmentManager.getFragments()
.get(fragmentManager.getBackStackEntryCount());
fragment.onResume(); //Make sure the fragment that is currently at the top of the stack calls its onResume method
}
}
}
My problem is that I open the app and go to this Activity which loads the fragments and then go through the flow to a certain stage ( I haven't narrowed it down yet) then I press the home button and blank my screen. Now after a certain amount of time when I open the app again it opens on the fragment I left but everything seems to be messed up, when I press back it seems to pop the wrong fragment and the UI becomes mixed up with the different fragments.
My guess is that when I open the app again the Activity onResume or the Fragment onResume or some lifecycle event is being called that I am not handling correctly?
So I was wondering is there best practices, guidelines or patterns that should be adhered to when using a Fragment pattern like I am doing so?
Since you have so many fragments in one activity, and they use the same container, that means all fragments are in the same place, and only one fragment will show at a time.
So why don't you use ViewPager and let FragmentPagerAdapter manager these fragments? In this way, you do not need to manager fragment lifecycle by yourself, you just need to override FragmentPagerAdapter methods:
to create fragment instance by getItem,
to update fragment by getItemPosition and Adapter.notifyDataSetChanged(),
to show selected fragment by mViewPager.setCurrentItem(i)
Code snippets, detail refer to https://github.com/li2/Update_Replace_Fragment_In_ViewPager/
private FragmentPagerAdapter mViewPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
#Override
public int getCount() {
return PAGE_COUNT;
}
// Return the Fragment associated with a specified position.
#Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem(" + position + ")");
if (position == 0) {
return Page0Fragment.newInstance(mDate);
} else if (position == 1) {
return Page1Fragment.newInstance(mContent);
}
return null;
}
#Override
// To update fragment in ViewPager, we should override getItemPosition() method,
// in this method, we call the fragment's public updating method.
public int getItemPosition(Object object) {
Log.d(TAG, "getItemPosition(" + object.getClass().getSimpleName() + ")");
if (object instanceof Page0Fragment) {
((Page0Fragment) object).updateDate(mDate);
} else if (object instanceof Page1Fragment) {
((Page1Fragment) object).updateContent(mContent);
}
return super.getItemPosition(object);
};
};
I'm having an special use case where I need to switch between two fragments. The issue I'm having is that for the second fragment I need to persist it's state, and the only thing that seems to be working for that is to add it to the BackStack.
I rely on the support fragment manager to replace the fragments:
public void toggle() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment instanceof FragmentB && null != fragmentA) {
// fragment B is visible - we should show fragment A
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.frag_fade_in, R.anim.frag_fade_out,
R.anim.frag_fade_in, R.anim.frag_fade_out)
.replace(R.id.fragment_container, fragmentA)
.commit();
} else if (fragment instanceof FragmentA && null != fragmentB) {
// fragment A is visible - we should show fragment B
boolean isRestored = false;
fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_B);
if (null != fragment) {
// Restore fragment state from the BackStack
fragmentB = (FragmentB) fragment;
isRestored = true;
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.frag_fade_in,
R.anim.frag_fade_out,
R.anim.frag_fade_in,
R.anim.frag_fade_out);
transaction.replace(R.id.fragment_container, fragmentB, TAG_FRAG_B);
if(!isRestored){
transaction.addToBackStack(TAG_FRAG_B)
}
transaction.commit();
} else {
// Just pop any fragments that were added - usually we won't get in here
getSupportFragmentManager().popBackStack();
}
}
This in combination with the onBackPressed() override:
#Override
public void onBackPressed() {
if (isCurrentFragmentB()) {
toggle();
} else {
// Back key was pressed and we are on fragment A - at this state we simply want to go back to the
// previous section
super.onBackPressed();
}
}
Using this implementation I make sure I reuse fragment B and keep it's state so that it doesn't look like it is created from scratch each time. I also make sure that when I go back, I can go only from fragment B to A and not from fragment A to B.
The issue I encountered is that when super.onBackPressed(); is called and more than one fragment was added(replaced actually, as I want only one active fragment at a time) through the fragment manager, it will throw an exception:
java.lang.IllegalStateException: Fragment already added: FragmentA{af9c26b #0 id=0x7f0e00d3}
This is happening only when the active fragment is FragmentA. I have a suspicion that this is because of the BackStack implementation, but as I've said, I only want the second one to be persisted.
How can I fix this? I am missing something?
I have managed to implement an work-around for this, although it is a little hacky.
Because I need to keep the state of FragmentB, I am forced to add it to the BackStack, but this will actually affect what transition is reversed when onBackPressed() is called.
To avoid this, I had to update the logic for the back press and manually handle that case
#Override
public void onBackPressed() {
if (isCurrentFragmentB()) {
toggle();
} else if (isCurrentFragmentA()) {
getSupportFragmentManager().popBackStackImmediate(TAG_FRAG_A, FragmentManager.POP_BACK_STACK_INCLUSIVE);
// Special case - because we added the fragment B to the BackStack in order to easily resume it's state,
// this will fail as it will actually try to add fragment A again to the fragment manager (it
// will try to reverse the last transaction)
super.finish();
} else {
// Usual flow - let the OS decide what to do
super.onBackPressed();
}
}
Also, I've optimized the toggle method a little bit:
public void toggle() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
#SuppressLint("CommitTransaction") FragmentTransaction transaction =
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.frag_fade_in, R.anim.frag_fade_out,
R.anim.frag_fade_in, R.anim.frag_fade_out);
if (fragment instanceof FragmentB && null != fragmentA) {
// fragment B is visible - we should show fragment A
fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_A);
if (null != fragment) {
// Restore fragment state from the BackStack
fragmentA = (FragmentA) fragment;
}
// Replace current fragment with fragment A and commit the transaction
transaction.replace(R.id.fragment_container, fragmentA, TAG_FRAG_A).commit();
} else if (fragment instanceof FragmentA && null != fragmentB) {
// fragment A is visible - we should show fragment B
fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_B);
if (null != fragment) {
// Restore fragment state from the BackStack
fragmentB = (FragmentB) fragment;
}
// Replace current fragment with fragment B
transaction.replace(R.id.fragment_container, fragmentB, TAG_FRAG_B);
if (null == fragment) {
// No entry of the fragment B in the BackStack, we want to add it for future uses
transaction.addToBackStack(TAG_FRAG_B);
}
// Commit the transaction
transaction.commit();
} else {
// Just pop any fragments that were added - usually we won't get in here
getSupportFragmentManager().popBackStack();
}
}
I hope this can help others which need an similar flow.
PS: The fragment I want to persist is SupportMapFragment, so that my map isn't always redrawn, re-centered and populated with data every time I want to show it.
I'm working on an android application, that uses a navigation drawer to switch between two fragments. However, each time I switch, the fragment is completely recreated.
Here is the code from my main activity.
/* The click listener for ListView in the navigation drawer */
private class DrawerItemClickListener implements ListView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectItem(position);
}
}
private void selectItem(int position) {
android.support.v4.app.Fragment fragment;
String tag;
android.support.v4.app.FragmentManager; fragmentManager = getSupportFragmentManager();
switch(position) {
case 0:
if(fragmentManager.findFragmentByTag("one") != null) {
fragment = fragmentManager.findFragmentByTag("one");
} else {
fragment = new OneFragment();
}
tag = "one";
break;
case 1:
if(fragmentManager.findFragmentByTag("two") != null) {
fragment = fragmentManager.findFragmentByTag("two");
} else {
fragment = new TwoFragment();
}
tag = "two";
break;
}
fragment.setRetainInstance(true);
fragmentManager.beginTransaction().replace(R.id.container, fragment, tag).commit();
// update selected item and title, then close the drawer
mDrawerList.setItemChecked(position, true);
setTitle(mNavTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
}
I've set up some debug logging, and every time selectItem is called, one fragment is destroyed, while the other is created.
Is there any way to prevent the fragments from being recreated, and just reuse them instead?
After #meredrica pointed out that replace() destroys the fragments, I went back through the FragmentManager documentation. This is the solution I've come up with, that seems to be working.
/* The click listener for ListView in the navigation drawer */
private class DrawerItemClickListener implements ListView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectItem(position);
}
}
private void selectItem(int position) {
android.support.v4.app.FragmentManager; fragmentManager = getSupportFragmentManager();
switch(position) {
case 0:
if(fragmentManager.findFragmentByTag("one") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("one")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
fragmentManager.beginTransaction().add(R.id.container, new OneFragment(), "one").commit();
}
if(fragmentManager.findFragmentByTag("two") != null){
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("two")).commit();
}
break;
case 1:
if(fragmentManager.findFragmentByTag("two") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("two")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
fragmentManager.beginTransaction().add(R.id.container, new TwoFragment(), "two").commit();
}
if(fragmentManager.findFragmentByTag("one") != null){
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("one")).commit();
}
break;
}
// update selected item and title, then close the drawer
mDrawerList.setItemChecked(position, true);
setTitle(mNavTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
}
I also added this bit, but I'm not sure if it's necessary or not.
#Override
public void onDestroy() {
super.onDestroy();
FragmentManager fragmentManager = getSupportFragmentManager();
if(fragmentManager.findFragmentByTag("one") != null){
fragmentManager.beginTransaction().remove(fragmentManager.findFragmentByTag("one")).commit();
}
if(fragmentManager.findFragmentByTag("two") != null){
fragmentManager.beginTransaction().remove(fragmentManager.findFragmentByTag("two")).commit();
}
}
Use the attach/detach method with tags:
Detach will destroy the view hirachy but keeps the state, like if on the backstack; this will let the "not-visible" fragment have a smaller memory footprint. But mind you that you need to correctly implement the fragment lifecycle (which you should do in the first place)
Detach the given fragment from the UI. This is the same state as when it is put on the back stack: the fragment is removed from the UI, however its state is still being actively managed by the fragment manager. When going into this state its view hierarchy is destroyed.
The first time you add the fragment
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.add(android.R.id.content, new MyFragment(),MyFragment.class.getSimpleName());
t.commit();
then you detach it
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.detach(MyFragment.class.getSimpleName());
t.commit();
and attach it again if switched back, state will be kept
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.attach(getSupportFragmentManager().findFragmentByTag(MyFragment.class.getSimpleName()));
t.commit();
But you always have to check if the fragment was added yet, if not then add it, else just attach it:
if (getSupportFragmentManager().findFragmentByTag(MyFragment.class.getSimpleName()) == null) {
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.add(android.R.id.content, new MyFragment(), MyFragment.class.getSimpleName());
t.commit();
} else {
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.attach(getSupportFragmentManager().findFragmentByTag(MyFragment.class.getSimpleName()));
t.commit();
}
The replace method destroys your fragments. One workaround is to set them to Visibility.GONE, another (less easy) method is to hold them in a variable. If you do that, make sure you don't leak memory left and right.
I did this before like this:
if (mPrevFrag != fragment) {
// Change
FragmentTransaction ft = fragmentManager.beginTransaction();
if (mPrevFrag != null){
ft.hide(mPrevFrag);
}
ft.show(fragment);
ft.commit();
mPrevFrag = fragment;
}
(you will need to track your pervious fragment in this solution)
I guess you can not directly manipulate the lifecycle mechanisms of your Fragments. The very fact that you can findFragmentByTag is not very bad. It means that the Fragment object is not recreated fully, if it is already commited. The existing Fragment just passes all the lifecycle steps each Fragment has - that means that only UI is "recreated".
It is a very convenient and useful memory management strategy - and appropriate, in most cases. Fragment which is gone, has the resources which have to be utilized in order to de-allocate memory.
If you just cease using this strategy, the memory usage of your application could increase badly.
Nonetheless, there are retained fragments, which lifecycle is a bit different and do not correspond to the Activity they are attached to. Typically, they are used to retain some things you want to save, for example, to manage configuration changes
However, the fragment [re]creation strategy depends on the context - that is, what you would like to solve, and what are the trade-offs that you are willing to accept.
Just find the current fragment calling getFragmentById("id of your container") and then hide it and show needed fragment.
private void openFragment(Fragment fragment, String tag) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment existingFragment = fragmentManager.findFragmentByTag(tag);
if (existingFragment != null) {
Fragment currentFragment = fragmentManager.findFragmentById(R.id.container);
fragmentTransaction.hide(currentFragment);
fragmentTransaction.show(existingFragment);
}
else {
fragmentTransaction.add(R.id.container, fragment, tag);
}
fragmentTransaction.commit();
}
Same idea as Tester101 but this is what I ended up using.
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment oldFragment = fragmentManager.findFragmentByTag( "" + m_lastDrawerSelectPosition );
if ( oldFragment != null )
fragmentTransaction.hide( oldFragment );
Fragment newFragment = fragmentManager.findFragmentByTag( "" + position );
if ( newFragment == null )
{
newFragment = getFragment( position );
fragmentTransaction.add( R.id.home_content_frame, newFragment, "" + position );
}
fragmentTransaction.show( newFragment );
fragmentTransaction.commit();
Hide easily in kotlin using extensions:
fun FragmentManager.present(newFragment: Fragment, lastFragment: Fragment? = null, containerId: Int) {
if (lastFragment == newFragment) return
val transaction = beginTransaction()
if (lastFragment != null && findFragmentByTag(lastFragment.getTagg()) != null) {
transaction.hide(lastFragment)
}
val existingFragment = findFragmentByTag(newFragment.getTagg())
if (existingFragment != null) {
transaction.show(existingFragment).commit()
} else {
transaction.add(containerId, newFragment, newFragment.getTagg()).commit()
}
}
fun Fragment.getTagg(): String = this::class.java.simpleName
Usage
supportFragmentManager.present(fragment, lastFragment, R.id.fragmentPlaceHolder)
lastFragment = fragment
Here's what I'm using for a simple 2 fragment case in Kotlin:
private val advancedHome = HomeAdvancedFragment()
private val basicHome = HomeBasicFragment()
override fun onCreate(savedInstanceState: Bundle?) {
...
// Attach both fragments and hide one so we can swap out easily later
supportFragmentManager.commit {
setReorderingAllowed(true)
add(R.id.fragment_container_view, basicHome)
add(R.id.fragment_container_view, advancedHome)
hide(basicHome)
}
binding.displayModeToggle.onStateChanged {
when (it) {
0 -> swapFragments(advancedHome, basicHome)
1 -> swapFragments(basicHome, advancedHome)
}
}
...
}
With this FragmentActivity extension:
fun FragmentActivity.swapFragments(show: Fragment, hide: Fragment) {
supportFragmentManager.commit {
show(show)
hide(hide)
}
}
How about playing with the Visible attribute?
this is a little late response.
if you're using view pager for fragments, set the off screen page limit of the fragment to the number of fragments created.
mViewPager.setOffscreenPageLimit(3); // number of fragments here is 3