121
votes

I came to know about ViewPager2 and tried to implement it, but didn't find any proper example.

Can anyone tell me how can I use it.

I am looking for proper usage, not an example.

10
Here is explanation and example check it : michaelevans.org/blog/2019/02/07/hands-on-with-viewpager2Mehul Kabaria
Whoever given close votes for recommend or find a book, tool, software library, tutorial or other off-site resource Please read carefully that I am not looking for example.Pratik Butani
appreciated question, look like Blog :DNikunj Paradva
@NikunjParadva Answer of Nilesh Rathod is much appreciated and also looks like blog post. The question is just simple.Pratik Butani

10 Answers

250
votes

UPDATE 7

Check : Migrate from ViewPager to ViewPager2

Check : Create swipe views with tabs using ViewPager2

UPDATE 6

Check out my answer if you want to implement Carousel using View Pager2

UPDATE 5

How to use TabLayout with ViewPager2

SAMPLE CODE

Use below dependencies

implementation 'com.google.android.material:material:1.1.0-alpha08'
implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'

SAMPLE CODE

XMl layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <com.google.android.material.appbar.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|enterAlways"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

        <com.google.android.material.tabs.TabLayout
                android:id="@+id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/viewpager"
            app:layout_anchor="@id/tabs"
            app:layout_anchorGravity="bottom"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
    />


</androidx.coordinatorlayout.widget.CoordinatorLayout>

Activity

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import com.google.android.material.tabs.TabLayoutMediator

import com.google.android.material.tabs.TabLayout


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

//        setSupportActionBar(toolbar)
        viewpager.adapter = AppViewPagerAdapter(supportFragmentManager, lifecycle)

        TabLayoutMediator(tabs, viewpager, object : TabLayoutMediator.OnConfigureTabCallback {
            override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
                // Styling each tab here
                tab.text = "Tab $position"
            }
        }).attach()


    }
}

OUTPUT

TabLayout with ViewPager2

From Docs

ViewPager2

New features

  • Right-to-left (RTL) layout support
  • Vertical orientation support
  • notifyDataSetChanged fully functional

API changes

  • FragmentStateAdapter replaces FragmentStatePagerAdapter
  • RecyclerView.Adapter replaces PagerAdapter
  • registerOnPageChangeCallback replaces addPageChangeListener

SAMPLE CODE

add the latest dependencies for ViewPager2

implementation 'androidx.viewpager2:viewpager2:1.0.0-alpha01'

layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

activity

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;

import java.util.ArrayList;

public class MyActivity extends AppCompatActivity {

    ViewPager2 myViewPager2;
    MyAdapter MyAdapter;
    private ArrayList<String> arrayList = new ArrayList<>();

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

        myViewPager2 = findViewById(R.id.view_pager);

        arrayList.add("Item 1");
        arrayList.add("Item 2");
        arrayList.add("Item 3");
        arrayList.add("Item 4");
        arrayList.add("Item 5");

        MyAdapter = new MyAdapter(this, arrayList);


        myViewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);

        myViewPager2.setAdapter(MyAdapter);
    }
}

MyAdapter

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    private Context context;
    private ArrayList<String> arrayList = new ArrayList<>();

    public MyAdapter(Context context, ArrayList<String> arrayList) {
        this.context = context;
        this.arrayList = arrayList;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.tvName.setText(arrayList.get(position));
    }

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

    public class MyViewHolder extends RecyclerView.ViewHolder {
        TextView tvName;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            tvName = itemView.findViewById(R.id.tvName);
        }
    }
}

New features

now we need to use ViewPager2.OnPageChangeCallback() to get Swipe event of ViewPager2

SAMPLE CODE

    myViewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels);
        }

        @Override
        public void onPageSelected(int position) {
            super.onPageSelected(position);

            Log.e("Selected_Page", String.valueOf(position));
        }

        @Override
        public void onPageScrollStateChanged(int state) {
            super.onPageScrollStateChanged(state);
        }
    });

we can set Orientation using myViewPager2.setOrientation()

SAMPLE CODE

For HORIZONTAL Orientation use

myViewPager2.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);

For VERTICAL Orientation use

myViewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);

We can use notifyDataSetChanged same as we are using in RecyclerView.Adapter

SAMPLE CODE to add new item

    btnAdd.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            arrayList.add("New ITEM ADDED");
            MyAdapter.notifyDataSetChanged();
        }
    });

SAMPLE CODE to remove new item

    btnRemove.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            arrayList.remove(3);
            MyAdapter.notifyItemRemoved(3);
        }
    });

UPDATE

Try this if you want to use Fragment with ViewPager2

First create a ViewPagerFragmentAdapter class which extends FragmentStateAdapter

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

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();
    }
}

Now use like this in your activity

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;
import neel.com.bottomappbar.R;

public class MainActivity extends AppCompatActivity {

    ViewPager2 myViewPager2;
    ViewPagerFragmentAdapter myAdapter;

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

        myViewPager2 = findViewById(R.id.view_pager);

        myAdapter = new ViewPagerFragmentAdapter(getSupportFragmentManager());

        // add Fragments in your ViewPagerFragmentAdapter class
        myAdapter.addFragment(new FragmentOne());
        myAdapter.addFragment(new Fragmenttwo());
        myAdapter.addFragment(new FragmentThree());

        // set Orientation in your ViewPager2
        myViewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);

        myViewPager2.setAdapter(myAdapter);

    }

}

for more information check this

UPDATE 2

Version 1.0.0-alpha02

New features

  • Ability to disable user input (setUserInputEnabled, isUserInputEnabled)

API changes

  • ViewPager2 class final

Bug fixes

  • FragmentStateAdapter stability fixes

SAMPLE CODE to disable swiping in viewpager2

myViewPager2.setUserInputEnabled(false);// SAMPLE CODE to disable swiping in viewpager2


myViewPager2.setUserInputEnabled(true);//SAMPLE CODE to enable swiping in viewpager2

UPDATE 3

Version 1.0.0-alpha03

New features

  • Ability to programmatically scroll ViewPager2: fakeDragBy(offsetPx).

API changes

  • FragmentStateAdapter now requires a Lifecycle object. Two utility constructors added to obtain it from the host FragmentActivity or the host Fragment

SAMPLE CODE

ViewPagerFragmentAdapter

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

public class ViewPagerFragmentAdapter extends FragmentStateAdapter {

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


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

    @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();
    }
}

MainActivity code

import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;
import neel.com.bottomappbar.R;

public class MainActivity extends AppCompatActivity {

    ViewPager2 myViewPager2;
    ViewPagerFragmentAdapter myAdapter;

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

        myViewPager2=findViewById(R.id.view_pager);
        myAdapter = new ViewPagerFragmentAdapter(getSupportFragmentManager(), getLifecycle());

        // add Fragments in your ViewPagerFragmentAdapter class
        myAdapter.addFragment(new FragmentOne());
        myAdapter.addFragment(new Fragmenttwo());
        myAdapter.addFragment(new FragmentThree());

        myViewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);

        myViewPager2.setAdapter(myAdapter);
    }
}

UPDATE 4

Version 1.0.0-alpha05 New features

  • ItemDecorator introduced with a behaviour consistent with RecyclerView.
  • MarginPageTransformer introduced to provide an ability to create space between pages (outside of page inset).
  • CompositePageTransformer introduced to provide an ability to combine multiple PageTransformers

API changes

  • FragmentStateAdapter#getItem method renamed to FragmentStateAdapter#createFragment - previous method name has proven to be a source of bugs in the past.
  • OFFSCREEN_PAGE_LIMIT_DEFAULT value changed from 0 to -1. No need for a client code change if the OFFSCREEN_PAGE_LIMIT_DEFAULTconstant used.

SAMPLE CODE

Activity code

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.MarginPageTransformer;
import androidx.viewpager2.widget.ViewPager2;
import neel.com.bottomappbar.R;

public class MainActivity extends AppCompatActivity {

    ViewPager2 myViewPager2;
    ViewPagerFragmentAdapter myAdapter;
    private ArrayList<Fragment> arrayList = new ArrayList<>();

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

        myViewPager2 = findViewById(R.id.myViewPager2);

        // add Fragments in your ViewPagerFragmentAdapter class
        arrayList.add(new FragmentOne());
        arrayList.add(new Fragmenttwo());
        arrayList.add(new FragmentThree());

        myAdapter = new ViewPagerFragmentAdapter(getSupportFragmentManager(), getLifecycle());
        // set Orientation in your ViewPager2
        myViewPager2.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);

        myViewPager2.setAdapter(myAdapter);

        myViewPager2.setPageTransformer(new MarginPageTransformer(1500));


    }
}

ViewPagerFragmentAdapter

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

public class ViewPagerFragmentAdapter extends FragmentStateAdapter {

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


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

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        switch (position) {
            case 0:
                return new FragmentOne();
            case 1:
                return new Fragmenttwo();
            case 2:
                return new FragmentThree();

        }
        return null;
    }

    @Override
    public int getItemCount() {
        return 3;
    }
}
11
votes

Actually now there is an official samples repo for ViewPager2 (linked below)

Repo contains following samples (Quoting from the repo readme below)

Samples

  • ViewPager2 with Views - shows how to set up a ViewPager2 with Views as pages
  • ViewPager2 with Fragments - shows how to set up a ViewPager2 with Fragments as pages
  • ViewPager2 with a Mutable Collection (Views) - demonstrates usage of ViewPager2 with Views as pages and mutations in a page adapter
  • ViewPager2 with a Mutable Collection (Fragments) - demonstrates usage of ViewPager2 with Fragments as pages, and mutations in a page adapter
  • ViewPager2 with a TabLayout (Views) - shows how to set up a ViewPager2 with Views as pages, and link it to a TabLayout

Simple example of ViewPager2 with Fragments in Kotlin

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".MainActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager2_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

</LinearLayout>

MainActivity.kt

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        val viewPager2 = findViewById<ViewPager2>(R.id.pager2_container)

        val fragmentList = arrayListOf(
            FirstFragment.newInstance(),
            SecondFragment.newInstance(),
            ThirdFragment.newInstance()
        )
        viewPager2.adapter = ViewPagerAdapter(this, fragmentList)
   }
}

FirstFragment.kt (SecondFragment.kt and ThirdFragment.kt looks similar to FirstFragment.kt)

class FirstFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    companion object{
        fun newInstance() = FirstFragment()
    }
}

ViewPagerAdapter.kt

class ViewPagerAdapter(fa:FragmentActivity, private val fragments:ArrayList<Fragment>): FragmentStateAdapter(fa) {

    override fun getItemCount(): Int = fragments.size

    override fun createFragment(position: Int): Fragment = fragments[position]

}

Some other useful resources:

6
votes

Use of ViewPager2 in Android

As mentioned on Developer Site

API changes

FragmentStateAdapter replaces FragmentStatePagerAdapter

RecyclerView.Adapter replaces PagerAdapter

registerOnPageChangeCallback replaces addPageChangeListener

In Simple Words they make it View Pager adapter work like Recycle View Adapter.

Note:- We don't need to use fragment in View Pager 2. It is fully depend on RecyclerView.Adapter inflate layout.

Here is sample gitHub repo Link

Example:-

MainActivity.class

public class MainActivity extends AppCompatActivity {
    
    private ViewPager2 mPager;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getSupportActionBar().setTitle("View Pager 2");
        mPager = findViewById(R.id.pager);
        mPager.setAdapter(new MyViewPagerAdapter(this));
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return super.onCreateOptionsMenu(menu);
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (R.id.change == item.getItemId()) {
            mPager.setOrientation(mPager.getOrientation() != ViewPager2.ORIENTATION_VERTICAL ? ViewPager2.ORIENTATION_VERTICAL : ViewPager2.ORIENTATION_HORIZONTAL);
        }
        return super.onOptionsItemSelected(item);
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</android.support.constraint.ConstraintLayout>

MyViewPagerAdapter.class

public class MyViewPagerAdapter extends RecyclerView.Adapter<MyHolder> {
    
    private Context context;
    
    public MyViewPagerAdapter(Context context) {
        this.context=context;
    }
    
    @NonNull
    @Override
    public MyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new MyHolder(LayoutInflater.from(context).inflate(R.layout.cell_item, parent, false));
    }
    
    @Override
    public void onBindViewHolder(@NonNull MyHolder holder, int position) {
      holder.mText.setText("Page "+(position+1));
    }
    
    @Override
    public int getItemCount() {
        return 10;
    }
}

cell_item.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="Page 1"
        android:textSize="20sp" />

</android.support.constraint.ConstraintLayout>

MyHolder.class

class MyHolder extends RecyclerView.ViewHolder {
    
    public TextView mText;
    
    public MyHolder(@NonNull View itemView) {
        super(itemView);
        mText = itemView.findViewById(R.id.text);
    }
}

output:

enter image description here

5
votes

Here is what I did to implemet ViewPager2 with TabLayout with 3 Fragment full Examble:

Layout contains ViewPager2 with TabLayout:

 <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/include3">

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tab_layout"
            android:layout_width="match_parent"
            android:background="@color/colorPrimary"
            app:tabTextColor="@color/tab_dismiss_color"
            app:tabSelectedTextColor="@color/green"
            android:layout_height="wrap_content" />

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:layout_gravity="bottom"
            android:background="#e4e4e4" />

        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/pager"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />
    </LinearLayout>

init ViewPager2 and set Tabs Name:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_report);
        ButterKnife.bind(this);
        actionBarTitleId.setText(R.string.reports);

        viewPager.setAdapter(new ViewPagerAdapter(this));

        new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
            switch (position) {
                case 0:
                    tab.setText(R.string.financial_duty_str);
                    break;
                case 1:
                    tab.setText(R.string.financial_unpaid_str);
                    break;
                case 2:
                    tab.setText(R.string.financial_paid_str);
                    break;

            }
        }).attach();

    }

ViewPager2 adapter :

public class ViewPagerAdapter extends FragmentStateAdapter {


    public ViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
        super(fragmentActivity);
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        switch (position) {
            case 0:
                return new FinancialFragment();
            case 1:
                return new FinancialUnPaidFragment();
            case 2:
                return new FinancialPaidFragment();
            default:
                return null;

        }
    }

    @Override
    public int getItemCount() {
        return 3;
    }

dependency used:

implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation 'com.google.android.material:material:1.1.0-alpha10'
1
votes

How to use it explained very clearly. Let me give small but very crucial tips and details about ViewPager2 especially if it's inside a fragment about how to prevent memory leaks

  1. Don't use the constructor that takes fragment especially if you are using TabLayout.

    public FragmentStateAdapter(@NonNull Fragment fragment) {
        this(fragment.getChildFragmentManager(), fragment.getLifecycle());
    }
    

because it has risk of memory leak as described here

instead use the one takes FragmentManager and LifeCycle. And don't send lifeCycle of fragment as argument, use viewLifeCycleOwner's lifecycle because viewLifeCycleOwner represents life cycle of fragment's view.

 class NavigableFragmentStateAdapter(
    fragmentManager: FragmentManager,
    lifecycle: Lifecycle
) : FragmentStateAdapter(fragmentManager, lifecycle) {


}

and set adapter with in onCreateView

viewPager.adapter =
            NavigableFragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle)
  1. If you are using TabLayout when ViewPager2 is inside a fragment do not forget to detach TabLayout and set adapter of ViewPager2 as

detach TabLayoutMediator since it causing memory leaks when it's in a fragment

https://stackguides.com/questions/61779776/leak-canary-detects-memory-leaks-for-tablayout-with-viewpager2

ViewPager2 inside a fragment leaks after replacing the fragment it's in by Navigation Component navigate

TabLayoutMediator(tabLayout, viewPager2, tabConfigurationStrategy).detach()     
viewPager2.adapter = null

inside onDestroyView method of Fragment

  1. If you wish to use pages of ViewPager as navHostFragment with navigation components to navigate back to child fragments register FragmentTransactionCallback in FragmentStateAdapter as

    private val fragmentTransactionCallback =
        object : FragmentStateAdapter.FragmentTransactionCallback() {
            override fun onFragmentMaxLifecyclePreUpdated(
                fragment: Fragment,
                maxLifecycleState: Lifecycle.State
            ) = if (maxLifecycleState == Lifecycle.State.RESUMED) {
    
                // This fragment is becoming the active Fragment - set it to
                // the primary navigation fragment in the OnPostEventListener
                OnPostEventListener {
                    fragment.parentFragmentManager.commitNow {
                        setPrimaryNavigationFragment(fragment)
                    }
                }
            } else {
                super.onFragmentMaxLifecyclePreUpdated(fragment, maxLifecycleState)
            }
        }
    
    init {
        // Add a FragmentTransactionCallback to handle changing
        // the primary navigation fragment
        registerFragmentTransactionCallback(fragmentTransactionCallback)
    }
    

Also if you wish to see some samples how to use ViewPager2 with navigation components, dynamic feature modules and combining with BottomNavigationView you can check out the tutorials in this github repo.

0
votes

Here my solution (Android Studio 3.6):

In app/build.gradle:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation('com.crashlytics.sdk.android:crashlytics:2.10.1@aar') { transitive = true; }
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.android.material:material:1.1.0-beta01'
    implementation 'org.altbeacon:android-beacon-library:2.16.3'
    implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
    implementation 'androidx.viewpager2:viewpager2:1.0.0-beta05'

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation "androidx.core:core-ktx:+"
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

Here my activity:

import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.widget.ViewPager2;

import java.util.ArrayList;
import java.util.List;

import com.myproject.android.R;
import com.myproject.android.adapter.CustomFragmentStateAdapter;
import com.myproject.android.ui.fragment.BluetoothPageFragment;
import com.myproject.android.ui.fragment.QrPageFragment;

public class QRBluetoothSwipeActivity extends AppCompatActivity {
    private ViewPager2 myViewPager2;
    private CustomFragmentStateAdapter 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() {
        List<Fragment> fragmentList = new ArrayList<Fragment>();
        QrPageFragment m1 = new QrPageFragment();
        BluetoothPageFragment m2 = new BluetoothPageFragment();
        myViewPager2 = findViewById(R.id.viewPager2);
        fragmentList.add(m2);
        fragmentList.add(m1);
        myAdapter = new CustomFragmentStateAdapter(this, fragmentList);
        myViewPager2.setAdapter(myAdapter);
    }
}

Here layout:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.actviity.SplashDelayActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager2"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Here my CustomFragmentStateAdapter

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;

import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;

public class CustomFragmentStateAdapter extends FragmentStateAdapter {
    private List<Fragment> listFragment = new ArrayList<>();

    public CustomFragmentStateAdapter(FragmentActivity fa, List<Fragment> list) {
        super(fa);
        listFragment = list;
    }

    @NotNull
    @Override
    public Fragment createFragment(int position) {
        return listFragment.get(position);
    }

    @Override
    public int getItemCount() {
        return 2;
    }
}

And here my fragments:

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.myproject.android.R;

public class BluetoothPageFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.bluetooth_page_fragment, container, false);
    }

}

and second fragment:

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.myproject.android.R;

public class QrPageFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.qr_page_fragment, container, false);
    }

}

And as result now I use androidx.viewpager2.widget.ViewPager2 with my custom fragments.

And it's work!!!

Nice.

P.S. Another implement:

public class CustomFragmentStateAdapter extends FragmentStateAdapter {
    private ArrayList<Fragment> arrayList = new ArrayList<>();

    public CustomFragmentStateAdapter (FragmentActivity fa) {
        super(fa);
    }

    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);
    }
}

And use like this (in activity):

 myViewPager2 = findViewById(R.id.viewPager2);
        myAdapter = new CustomFragmentStateAdapter (this);
        myAdapter.addFragment(new QrPageFragment());
        myAdapter.addFragment(new BluetoothPageFragment());
        myViewPager2.setAdapter(myAdapter);
0
votes

I first created a view pager, then migrated to view pager 2 using the instructions given in: https://developer.android.com/training/animation/vp2-migration

0
votes

This is the correct implementation!

typealias FragmentBuilder = () -> Fragment

class MyAdapter(
    fragmentManager: FragmentManager, 
    lifecycle: Lifecycle
) : FragmentStateAdapter(fragmentManager, lifecycle) {

    private val fragmentBuilders = mutableListOf<FragmentBuilder>()

    fun add(fragmentBuilder: FragmentBuilder) {
        fragmentBuilders.add(fragmentBuilder)
    }

    /**
     * Dynamic replacement of fragments
     */
    fun set(position: Int, fragmentBuilder: FragmentBuilder) {
        fragmentBuilders[position] = fragmentBuilder
    }

    override fun getItemCount() = fragmentBuilders.size

    override fun createFragment(position: Int) = fragmentBuilders[position].invoke()

}

Don't even thank)

0
votes

here is kotlin version implementation of @sushildlh answer in Kotlin.
in this code I am implementing viewpager2 with recyclerview to view images. also i am using viewbinding

food_details.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".presentation.recipeitem.RecipeDetailsFragment">


    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager2"
        android:layout_width="0dp"
        android:layout_height="300dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:src="@tools:sample/avatars" />
...

the fragment which inflates this xml

@AndroidEntryPoint class RecipeDetailsFragment : Fragment(R.layout.fragment_recipe_details) {

private val viewModel: RecipeItemViewModel by viewModels()
private val viewBinding: FragmentRecipeDetailsBinding by viewBinding()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    bindRecipeData(//object of data that is to be desplayed)
}

private fun bindRecipeData(recipeDetailedInfo: RecipeDetailedInfo?) {
    recipeDetailedInfo?.let {
        with(viewBinding) {
            viewPager2.adapter = ViewPagerAdapter(it.images)
            viewPager2.setPageTransformer(ZoomOutPageTransformer())
            lifecycleScope.launchWhenCreated {
                delay(500)
                viewPager2.setCurrentItem(if (it.images.size >= 2) 1 else 0, true)
            }
        }
    }
}....

in the fragment i am creating object of the adapter and sending directly list of strings that contains the images URLs

here is the viewpager adapter which is basically a normal recyclerview adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.blogspot.soyamr.recipes2.databinding.ImageviewBinding
import com.squareup.picasso.Picasso

class ViewPagerAdapter(private val images: List<String>) :
    RecyclerView.Adapter<ViewPagerAdapter.ImageViewHolder>() {


    class ImageViewHolder(private val imageViewBinding: ImageviewBinding) :
        RecyclerView.ViewHolder(imageViewBinding.root) {
        fun bind(imageLink: String) {
            Picasso.get().load(imageLink).into(imageViewBinding.root)
        }

        companion object {
            fun from(parent: ViewGroup): ImageViewHolder {
                val itemBinding =
                    ImageviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                return ImageViewHolder(itemBinding)
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
        return ImageViewHolder.from(parent)
    }

    override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
        holder.bind(images[position])
    }

    override fun getItemCount(): Int = images.size

}

in the recyclerview i am inflating this imageview.xml

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.imageview.ShapeableImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:scaleType="centerCrop"
    android:theme="@style/roundedImageView"
    />

you use your complex xml view if needed

0
votes

Simple example with two differente fragments, usin tabs. Main layout:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mainConstraintLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/mainViewPager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/mainTabLayout">

    </androidx.viewpager2.widget.ViewPager2>

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/mainTabLayout"
        android:layout_width="match_parent"
        android:layout_height="@dimen/toolbar_height"
        android:background="@color/brown_normal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:tabIndicatorColor="@color/yellow_light"
        app:tabIndicatorHeight="3dp"
        app:tabSelectedTextColor="@color/yellow_light"
        app:tabTextColor="@color/yellow_normal"
        tools:ignore="SpeakableTextPresentCheck" />
</androidx.constraintlayout.widget.ConstraintLayout>

Adapter:

public class MainToolsAdapter extends FragmentStateAdapter
{
    // The quantity of tab is fixed
    private static final int FRAGMENT_COUNT = 2;
    // Titles for each tab    
    private final String[] titles = new String[FRAGMENT_COUNT];

    public MainToolsAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, Context context)
    {
        super(fragmentManager, lifecycle);
        // Load titles for tab from resourses
        titles[0] = context.getResources().getString(R.string.tab_1);
        titles[1] = context.getResources().getString(R.string.tab_2);
    }

    @NonNull
    @Override
    public Fragment createFragment(int position)
    {
        // Create fragments according to position
        if(position == 0)
        {
            return new FragmentTabOne();
        }
        return new FragmentTabTwo();
    }

    @Override
    public int getItemCount()
    {
        return FRAGMENT_COUNT;
    }

    public String getItemTitle(int position)
    {
        if(position == 0)
        {
            return titles[0];
        }
        return titles[1];
    }
}

Main activity OnCreate:

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

    // Create adapter for ViewPager
    mainToolsAdapter = new MainToolsAdapter(getSupportFragmentManager(), getLifecycle(), this);

    // Set adapter to the ViewPager
    viewPager = findViewById(R.id.mainViewPager);
    viewPager.setAdapter(mainToolsAdapter);

    tabLayout = findViewById(R.id.mainTabLayout);

    // Create the TabLayoutMediator to asociate ViewPager2 to TabLayout
    TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager, true, new TabLayoutMediator.TabConfigurationStrategy()
    {
        @Override
        public void onConfigureTab(@NonNull TabLayout.Tab tab, int position)
        {
            // When a tab is created this is called, then we can set tab properties, in this case the text
            tab.setText(mainToolsAdapter.getItemTitle(position));
        }
    });
    // After configure we need to realice attach then Tab and ViewPager2 are asociated
    tabLayoutMediator.attach();
}

gradle:

plugins {
    id 'com.android.application'
}

android {
    compileSdk 30

    defaultConfig {
        applicationId "com.xxxx.yyyy"
        minSdk 19
        targetSdk 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies
{
    implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')

    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
    testImplementation 'junit:junit:4.13.2'

    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}