Search code examples
androidandroid-fragmentsandroid-viewpager

Android: FragmentPagerAdapter not working with ViewPager and material TabLayout


I am setting up a ViewPager which will render fragments depending of the selected tab in a TabLayout from material. I have a fragment which will be rendering all of this as the parent view (Which i don't know if this could be the cause.), and i am doing all this flow because i would like to set the data flow to be like Master Detail flow in which an activity will show 2 fragments, one of this will have a pager that displays another fragment in a ViewPager

So the fragment that has both the TabLayout and ViewPager have the class defined like this:

public class InstructionsFragment extends Fragment {
    private static final String TAG = InstructionsFragment.class.getSimpleName();

    private TabLayout instructionsTab;
    private ViewPager instructionsPager;

    private int recipeIndex;

    public InstructionsFragment(int recipeIndex) {
        this.recipeIndex = recipeIndex;
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_instructions, container, false);

        this.loadViews(rootView);

        return rootView;
    }

    @Override
    public void onStart() {
        super.onStart();

        this.loadPager();
    }

    private void loadViews(View view) {
        Log.i(TAG, "Loading views");

        this.instructionsTab = view.findViewById(R.id.recipe_instructions_tabs);
        this.instructionsPager = view.findViewById(R.id.recipe_instructions_pager);

        this.instructionsTab.setupWithViewPager(this.instructionsPager);
    }

    private void loadPager() {
        Log.i(TAG, "Loading pager");

        FragmentManager fragmentManager = this.getFragmentManager();
        IngredientsFragment ingredientsFragment = new IngredientsFragment(recipeIndex);

        InstructionsPagerAdapter instructionsAdapter = new InstructionsPagerAdapter(fragmentManager, 0);
        instructionsAdapter.addFragment(ingredientsFragment);

        this.instructionsPager.setAdapter(instructionsAdapter);
    }

This fragment makes use of the layout defined like this:

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

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

        <com.google.android.material.tabs.TabItem
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="@string/recipe_detail_tab_ingredients_label" />

        <com.google.android.material.tabs.TabItem
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="@string/recipe_detail_tab_steps_label" />
    </com.google.android.material.tabs.TabLayout>

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/recipe_instructions_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

So there's basically 2 static tabs and i want to test with just one, because even when i delete one of the tabs it still doesn't work.

The FragmentPagerAdater is defined in a class like this:

public class InstructionsPagerAdapter extends FragmentPagerAdapter {
    private List<Fragment> fragments = new ArrayList<>();

    public InstructionsPagerAdapter(@NonNull FragmentManager fm, int behavior) {
        super(fm, behavior);
    }

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

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

    @Override
    public int getCount() {
        return this.fragments.size();
    }
}

When going to the activity which displayes the InstructionsFragment, The tabs for some reason are not displaying and neither is the fragment selected by default. I did make some debugging in to see if there's an error or if the selected fragment by the viewPager is running, and i don't see any error logs in logcat and the selected fragment is actually running, so i don't know how to continue.

This is how the screen is displaying all of the code i've implemented: Image loading the pager

And this is how it looks without loading the pager code: Image without loading the pager code

I am running the application on Android 9 and these are the dependencies in my project:

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

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'com.google.code.gson:gson:2.8.6'
    implementation 'com.google.android.material:material:1.1.0'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

Solution

  • I was able to make it work by adding tabs dynamically, it looks like TabsLayout from material doesn't support having a static set of tabs in layout while using a ViewPager. The ViewPager must have an adapter which feeds the title of the tab for the selected fragment to display in the ViewPager.

    The end code for the FragmentPagerAdapter looks like this:

    public class InstructionsPagerAdapter extends FragmentPagerAdapter {
        private List<Fragment> fragments = new ArrayList<>();
        private List<String> pageTitles = new ArrayList<>();
    
        public InstructionsPagerAdapter(@NonNull FragmentManager fm, int behavior) {
            super(fm, behavior);
        }
    
        public void addFragment(Fragment fragment, String tabTitle) {
            this.fragments.add(fragment);
            this.pageTitles.add(tabTitle);
        }
    
        @NonNull
        @Override
        public Fragment getItem(int position) {
            return this.fragments.get(position);
        }
    
        @Override
        public int getCount() {
            return this.fragments.size();
        }
    
        @Nullable
        @Override
        public CharSequence getPageTitle(int position) {
            return this.pageTitles.get(position);
        }
    }
    

    Adding a list of titles for the fragments and select it when a fragment is selected.