Prevent fragment from loading data every time on bottomnavigation tab click

I am using BottomNavigationView with 5 tabs. Initially, I load 1st tab once the app starts. When I click on any other tab, an API call is made and data is loaded. Now When I click on the previous tab again (which was loaded on startup) the API call is made to load the same data again.

I do not want to load data again and want to show the fragment with the same data without a fresh API call.

My code:

public class MainActivity extends AppCompatActivity implements NavigationBarView.OnItemSelectedListener {

    private BottomNavigationView bottomNavigationView;
    public static ViewPager2 viewPager;

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

        viewPager = findViewById(R.id.viewpager);
        bottomNavigationView = findViewById(R.id.navigation);

        bottomNavigationView.setOnItemSelectedListener(this);

        getTabs();
    }

    public void getTabs() {

        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentAdapterMain adapter = new FragmentAdapterMain(fragmentManager, getLifecycle());
        viewPager.setAdapter(adapter);

        viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                switch (position) {
                    case 0:
                        bottomNavigationView.getMenu().findItem(R.id.one).setChecked(true);
                        break;
                    case 1:
                        bottomNavigationView.getMenu().findItem(R.id.two).setChecked(true);
                        break;
                    case 2:
                        bottomNavigationView.getMenu().findItem(R.id.three).setChecked(true);
                        break;
                    case 3:
                        bottomNavigationView.getMenu().findItem(R.id.four).setChecked(true);
                        break;
                    case 4:
                        bottomNavigationView.getMenu().findItem(R.id.five).setChecked(true);
                        break;
                }
            }
        });

    }

    @SuppressLint("NonConstantResourceId")
    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {

        switch (item.getItemId()) {
            case R.id.one:
                viewPager.setCurrentItem(0, false);
                break;
            case R.id.two:
                viewPager.setCurrentItem(1, false);
                break;
            case R.id.three:
                viewPager.setCurrentItem(2, false);
                break;
            case R.id.four:
                viewPager.setCurrentItem(3, false);
                break;
            case R.id.five:
                viewPager.setCurrentItem(4, false);
                break;
        }
        return true;
    }
}

I do not want to use viewPager.setOffscreenPageLimit(size) because it loads data in all 5 fragments on startup at once (I don’t want to call all 5 APIs on startup).

Answer

I don’t know if that’s possible as using navigation components whenever you use it, it pops the fragments stack.

but for keeping the data there’s a way I think by using a ViewModel which will stay alive even if the fragment is destroyed.

  • to start using ViewModels add this to dependencies section in build.grade (Module:app) :
dependencies {
...
    implementation "androidx.lifecycle:lifecycle-viewmodel:2.3.1"
    implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"

...
}
  • you should now be able to create a class that extends ViewModel, as an example I will call it myViewModel, in the ViewModel Constructor you will call the method that loads the data for you:
public class MyViewModel extends ViewModel {
    DataClass myData ;
    public MyViewModel () {
        super();
        getData();
    }
    void getData(){
    ...
    assign myData here 
    ...
    }

}
  • then in onCreate of the first fragment you would create the ViewModel using this code :
MyViewModel myViewMode= new ViewModelProvider(requireActivity()).get(MyViewModel .class);
  • now you would expect that everytime the fragment is created from zero will create also the ViewModel from the beginning, but here is the twist, the ViewModel stays alive as long as the Object you gave a reference to it through the ViewModelProvider is alive which in our case is the main activity.

  • that way the ViewModel will call its constructor only once during the whole lifetime of the application as long as your activity doesn’t get destroyed, and everytime afterwards you ask for the ViewModel in your fragment onCreate, it will just give you a reference to the previously created one instead of creating an new one (like the singleton design pattern if you encountered design patterns before).

  • You can later add a getter to your ViewModel to get the data whenever you want in your fragment.

N.B : the data you bring and store in the ViewModel consumes from your RAM, so avoid having a lot of data and store only what’s necessary, so as an example instead of storing a bitmap, store its uri/ instead of storing an object that have a key/id that belongs to a database, only store the key/id and whenever you need just bring it to memory from database.

the previous notice is important to avoid OutOfMemoryError Exception during your application use.

if you have the curiosity about ViewModels you can start reading about them through this link