Search code examples
javaandroidandroid-fragmentsandroid-tablayoutfragmentpageradapter

How to understand getFragments.size() giving me 2 when I've got 3 tabs in TabLayout each showing the right fragment?


I've got TabLayout with 3 tabs created via ViewPager using FragmentPagerAdapter and 3 fragments. Swiping to the third tab, breaks one bit of the first tab and changes the indexing of the fragments.

I've got TabLayout with 3 tabs created via ViewPager using FragmentPagerAdapter and 3 fragments. The fragments are OK, and the activity displays everything correctly on (re)start, but I've noticed a bug where when I swipe to the third tab, and then I swipe back to the first tab, the first tab loses some information. When I restart the activity, the first tab comes back with the correct information. But then loses it every time I go to the third, and then back to the first.I applied a workaround which refreshes the missing bit in fragment's onResume and this works fine. But I am still perplexed by this and trying to find why this is happening.

I have checked a variety of sources on the web, and went through my code several times but I don't see what could cause this problem. I have noticed however that tabs.getTabCount() shows 3 when I run it in my activity, but FragmentManager getFragments.size() surprisingly shows 2, when all three tabs and fragments are visible in the activity. It also shows that the indexes of fragments are changed when I swipe to the third tab: I would expect, tab1 to be at 0, tab2 at 1 and tab3 at 2, and this is the case for tab1 and tab2 (tab3 not being visible by FragmentManager at all) before I swipe to tab3, then the indexes are reversed: tab1 is at 1, tab2 is at 0, tab3 still not there although visible in the activity.

I get no error messages.

//I've got this in my activity
SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
    ViewPager viewPager = findViewById(R.id.view_pager);
    viewPager.setAdapter(sectionsPagerAdapter);
    TabLayout tabs = findViewById(R.id.tabs);
    tabs.setupWithViewPager(viewPager);
//this is my FragmentPagerAdapter
public class SectionsPagerAdapter extends FragmentPagerAdapter {

  private static final int NUM_TABS = 3;
  @StringRes
  private static final int[] TAB_TITLES = new int[]{R.string.tab_text_1, R.string.tab_text_2, R.string.tab_text_3};
  private final Context mContext;
  private final FragmentManager fm;

  public SectionsPagerAdapter(Context context, FragmentManager fm) {
    super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
    mContext = context;
    this.fm = fm;

  }

  @Override
  public Fragment getItem(int position) {
    // getItem is called to instantiate the fragment for the given page.
    // Return a PlaceholderFragment (defined as a static inner class below).
    //return PlaceholderFragment.newInstance(position + 1);
    Fragment frag = null;

    switch(position){
      case 0:
        frag = new InspectionInfoFragment();
        break;
      case 1:
        frag = new TuberConditionFragment();
        break;
      case 2:
        frag = new FindingsFragment();
        break;
    }

    return frag;

  }

  @Nullable
  @Override
  public CharSequence getPageTitle(int position) {
    return mContext.getResources().getString(TAB_TITLES[position]);
  }

  @Override
  public int getCount() {
    return NUM_TABS;
  }
}
<!-- this is my activity 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"
    tools:context=".view.inspection_form.InspectionFormActivity">

    <com.google.android.material.appbar.AppBarLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:theme="@style/AppTheme.AppBarOverlay">

      <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar_inspection_form"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:popupTheme="@style/AppTheme.PopupOverlay"
          />
      <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
          app:tabGravity="fill"
          app:tabMode="fixed">

      </com.google.android.material.tabs.TabLayout>

    </com.google.android.material.appbar.AppBarLayout>

    <androidx.viewpager.widget.ViewPager
      android:id="@+id/view_pager"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
      android:id="@+id/fab"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="bottom|end"
      android:layout_margin="@dimen/fab_margin"
      app:srcCompat="@drawable/outline_assignment_black_48" />
  </androidx.coordinatorlayout.widget.CoordinatorLayout>
This is my gradle module:app

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.sasa.spcsinspections"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables.useSupportLibrary = true

        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
            }
        }
    }
    buildTypes {
        debug {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility = '1.8'
        targetCompatibility = '1.8'
    }

    dataBinding{
        enabled true
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.google.android.material:material:1.1.0-alpha02'
    implementation 'androidx.annotation:annotation:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
    implementation 'androidx.preference:preference:1.1.0-alpha05'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha06'
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.google.code.gson:gson:2.8.5'
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.9.1'
    implementation "androidx.room:room-runtime:$rootProject.roomVersion"
    annotationProcessor "androidx.room:room-compiler:$rootProject.roomVersion"
    androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"
    implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion"
    annotationProcessor "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion"
}
// Not sure if it makes sense to include code for the three fragments + their layout files, I checked them, they are pretty straightforward and I don't see anything that would cause the problem... 

I would expect to have getFragments.size() giving me 3 and not having the weird losing a bit of tab1 and re-indexing.

I would be grateful if anyone could share their experience (if they have any) with this or similar problem or any ideas on how to get to the bottom of this.


Solution

  • Basically, that happens, because the adapter holds the strong reference only on fragments-neighbors.
    When you are on the first fragment, it stores 1,2 fragments.
    When you are on the third fragment, it stores 3,2 fragments.
    So, if you call getFragments() when you are on the middle fragment, 90% this method will return you 3.