9

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

Alexei
  • 14,350
  • 37
  • 121
  • 240

1 Answers1

16

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
}
Zain
  • 37,492
  • 7
  • 60
  • 84
  • 1
    I found solution https://stackoverflow.com/questions/54643379/use-of-viewpager2-in-android/58527394#58527394 – Alexei Oct 23 '19 at 16:39
  • 1
    Use this constructor: public ViewPagerFragmentAdapter(FragmentActivity fa) { super(fa); } – Alexei Oct 23 '19 at 16:45
  • 1
    @a_subscriber thanks for this tip, it works as well – Zain Oct 23 '19 at 16:46
  • 16
    This is a *VERY* wrong implementation. As naming imply, FragmentStateAdapter's `createFragment` should be responsible for instantiate Fragment. Creation of Fragment shouldn't happen outside `FragmentStateAdapter`. `FragmentStateAdapter` will be responsible for handling memory usage efficiency, even you need to create a large number of Fragments. If you create Fragment outside `FragmentStateAdapter`, it is highly not memory efficiency. – Cheok Yan Cheng May 05 '20 at 06:23
  • 1
    @CheokYanCheng thanks for this info . appreciated – Zain May 05 '20 at 21:41
  • @CheokYanCheng So how i "can dynamically modify the fragment collection at runtime, and ViewPager2 will correctly display the modified collection." as [documentation](https://developer.android.com/training/animation/vp2-migration#modifiable-fragments) say? – ramaral May 08 '20 at 14:04
  • I have the similar requirement where I need to add/remove/move/modify Fragments. I believe you need to override `getItemId` and `containsItem` correctly. I still in the midway of testing `viewpager2`. I will confirm with you whether my provided info is correct, after I finished with all testing. – Cheok Yan Cheng May 09 '20 at 16:16
  • What is purpose of overriding "containsItem" and "getItemId"? Are these overrides required if one is passing in new lists to the FragmentStatePagerAdapter implementation and calling notifyDataSetChanged? – the_prole Jul 14 '21 at 16:52
  • @CheokYanCheng What is purpose of overriding "containsItem" and "getItemId"? Are these overrides required if one is passing in new lists to the FragmentStatePagerAdapter implementation and calling notifyDataSetChanged? Asked a question here: https://stackoverflow.com/questions/68382901/what-is-purpose-of-overriding-containsitem-and-getitemid-in-fragmentstatead – the_prole Jul 14 '21 at 19:24
  • @all I've enhanced the answer to be more efficient like it should be – Zain Nov 10 '21 at 07:26