Why FragmentStateAdapter not work in ViewPager2?

Viewed 22332

Android Studio 3.6

I want in my activity to use 2 fragments inside ViewPager2.

Here my activity:

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;
public class QRBluetoothSwipeActivity extends AppCompatActivity {
    private ViewPager2 myViewPager2;
    private ViewPagerFragmentAdapter myAdapter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        // Make sure this is before calling super.onCreate
        setTheme(R.style.AppTheme); // show splash screen
        super.onCreate(savedInstanceState);
        setContentView(R.layout.qr_bluetooth_swipe_activity);
        init();
    }

    private void init() {
        myAdapter = new ViewPagerFragmentAdapter(getSupportFragmentManager());
        myAdapter.addFragment(new BluetoothPageFragment());
        myAdapter.addFragment(new QrPageFragment());
        myViewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
        myViewPager2.setAdapter(myAdapter);
    }

}

here ViewPagerFragmentAdapter

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.viewpager2.adapter.FragmentStateAdapter;

import java.util.ArrayList;

public class ViewPagerFragmentAdapter  extends FragmentStateAdapter {

    private ArrayList<Fragment> arrayList = new ArrayList<>();

    public ViewPagerFragmentAdapter(@NonNull FragmentManager fragmentManager) {
        super(fragmentManager);
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {
        return arrayList.get(position);
    }

    public void addFragment(Fragment fragment) {
        arrayList.add(fragment);
    }

    @Override
    public int getItemCount() {
        return arrayList.size();
    }
}

but I get compile error:

ViewPagerFragmentAdapter is not abstract and does not override abstract method createFragment(int) in FragmentStateAdapter

How I can fix this and use FRAGMENTS in ViewPager2 ?

Thanks

1 Answers

FragmentStateAdapter in ViewPager2 is a little bit different than in ViewPager as follows:

  • Instead of implementing getCount(), implement getItemCount()

  • Instead of implementing getItem(int position), implement createFragment(int position)

  • Constructor signature is different by adding Lifecycle argument which should be included in super as well

So replace your ViewPagerFragmentAdapter with below

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager2.adapter.FragmentStateAdapter;

import java.util.ArrayList;


public class ViewPagerFragmentAdapter extends FragmentStateAdapter {

    private ArrayList<Fragment> arrayList = new ArrayList<>();

    public ViewPagerFragmentAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
        super(fragmentManager, lifecycle);
    }


    public void addFragment(Fragment fragment) {
        arrayList.add(fragment);
    }

    @Override
    public int getItemCount() {
        return arrayList.size();
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        // return your fragment that corresponds to this 'position'
        return arrayList.get(position);
    }

}

Also make sure to import/extend the right adapter of ViewPager2

import androidx.viewpager2.adapter.FragmentStateAdapter;

UPDATE 2021

This answered the original issue of the question; However creating the FragmentStateAdapter this way is not that right generally speaking; because of:

  1. To have a high performant ViewPager, it should keep only a few instantiated off-screen page fragments.

  2. Keeping all the page fragments into a list is not good in terms of wasting device resources.

  3. createFragment(), as its name implies, is intended to create a new fragment for a particular page and return it; not to return a fragment from a list that has already instantiated fragments.

  4. Above all as per documentation createFragment()::

Provide a new Fragment associated with the specified position.

The adapter will be responsible for the Fragment lifecycle:

  • The Fragment will be used to display an item.
  • The Fragment will be destroyed when it gets too far from the viewport, and its state will be saved. When the item is close to the viewport again, a new Fragment will be requested, and a previously saved state will be used to initialize it.

So, I'd suggest to change this design to include that into consideration:

Assuming your page fragment is PageFragment; then you can have single instance pattern by having static PageFragment newInstance(int position) {} method in the PageFragment, and then the createFragment() should be then:

@NonNull
@Override
public Fragment createFragment(int position) {
    // return a brand new fragment that corresponds to this 'position'
    return PageFragment.newInstance(position); // Create a new instance of the page fragment
}
Related