3

My app uses a BottomNavigationBar to switch between Fragments, and it does it this way:

public class MainActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        BottomNavigationView bottomNav = findViewById(R.id.barra);
        bottomNav.setOnNavigationItemSelectedListener(navListener);

        getSupportFragmentManager().beginTransaction().replace(R.id.container, new KeyboardFragment()).commit();
        bottomNav.setSelectedItemId(R.id.keyboard);

    }


    private BottomNavigationView.OnNavigationItemSelectedListener navListener =
            new BottomNavigationView.OnNavigationItemSelectedListener() {
                @Override
                public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                    Fragment selectedFragment = null;

                    switch (item.getItemId()){
                        case R.id.camera:
                            selectedFragment = new CameraFragment();
                            break;
                        case R.id.keyboard:
                            selectedFragment = new KeyboardFragment();
                            break;
                        case R.id.settings:
                            selectedFragment = new SettingsFragment();
                            break;
                    }

                    getSupportFragmentManager().beginTransaction().replace(R.id.container,
                            selectedFragment).commit();

                    return true;

                }
            };
}

I want those Fragments to be static, so their content and views do not dissapear when I switch between them. I have tried to create them inside MainActivity's onCreate() method, but it only helps with retaining text inside EditText widgets, the rest of the views and content dissapear.

I have seen other similar questions, but they are answered poorly and I am new to this. Following some answers to similar questions, I have tried to use functions like add() or attach() instead of replace() but I don't think I'm doing it well; in fact sometimes my app crashes.

Should I paste here my layout.xml files too? The fragments are "displayed" into a simple FrameLayout. Thanks beforehand :)

3 Answers3

4

This is how I solved the problem succesfully:

  • First of all, all of the Fragments are declared as fields inside MainActivity class, as well as the variable selected, which will be used later:
public class MainActivity extends AppCompatActivity {

    public KeyboardFragment keyboard_fragment = new KeyboardFragment();
    public CameraFragment camera_fragment = new CameraFragment();
    public SettingsFragment settings_fragment = new SettingsFragment();
    
    Fragment selected = teclado_fragment;`

    //...
  • Then, the following methods are defined inside the class too, where R.id.container is the FrameLayout or whatever view that is being used to display the inflated Fragments:
    private void createFragment(Fragment fragment){
                 getSupportFragmentManager().beginTransaction()
                .add(R.id.container, fragment)
                .hide(fragment)
                .commit();
    }
    private void showFragment(Fragment fragment){
                 getSupportFragmentManager().beginTransaction()
                .show(fragment)
                .commit();
    }
    private void hideFragment(Fragment fragment){
                 getSupportFragmentManager().beginTransaction()
                .hide(fragment)
                .commit();
    }
  • Finally, whatever menu's listener is defined this way inside MainActivity's OnCreate() method:
private BottomNavigationView.OnNavigationItemSelectedListener navListener =
            new BottomNavigationView.OnNavigationItemSelectedListener() {
                @Override
                public boolean onNavigationItemSelected(@NonNull MenuItem item) {

                    switch (item.getItemId()){
                        case R.id.camera:
                            hideFragment(selected);
                            selected = camera_fragment;
                            showFragment(seleccionado);
                            break;
                        case R.id.keyboard:
                            hideFragment(selected);
                            selected = keyboard_fragment;
                            showFragment(seleccionado);
                            break;
                        case R.id.settings:
                            hideFragment(selected);
                            selected = settings_fragment;
                            showFragment(seleccionado);
                            break;
                    }

                    return true;

                }
            };

This way, the menu just hides and shows Fragments visually, and they are only declared once not to be destroyed until the app closes, thus mantaining all their fields and views in memory as long as the app is running.

2

replace() means destroy this one and add new one at its place and as your code is right now you can't use add() either because according to that switch case you will create a new fragment instance every time you navigate. It will waste memory and eventually app is going to crash with OutOfMemoryException

What can you do?

unfortunately with bottom navigation there are not much options but you can improve using following options

1 use viewmodels for each fragment and attach all viewmodels with host activiy that way you will not have to load data every time that fragment is created, data will survive in the viewmodels

2 or use a viewpager to hold your fragments a viewpager will be able to hold all of them in memory and to see the desired fragment , set the current fragment programatically by calling setCurrentItem() on the viewpager from OnNavigationItemSelected()

3 use add() instead of replace() and make your fragments are singleton so that you don't load data every time (if you are not using view model) also keep track of last fragment now if a user goes to another fragment and before going anywhere else return to this one you just pop the back using getSupportFragmantManager().popBackStack() to remove this fragment stack, otherwise pop the back stack and add that other fragment

4 use some other form of navigation see navigation components to make your life little easier

maybe there are better solution but that is all i could think of according to my experience which is not very much , although navigation components also do the replacing thing i don't think it is very bad if you use view models to hold the data.

Happy Coding

Abhinav Chauhan
  • 1,304
  • 1
  • 7
  • 24
0

The first Check fragment is exist or not if its exist show the fragment and hide the old fragment

show Fragment fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag(tag)).commit();

Hide Fragment fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag(HomeFragment.class.getSimpleName())).commit();

Second If Not Exist Add the Fragment fragmentManager.beginTransaction().add(R.id.fragment_activity, fragment, tag).commit();