0

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.

djevulen
  • 112
  • 1
  • 8

2 Answers2

1

Just in case anyone finds themselves in a similar situation as I did:

samaromku provided the answer to my above question, but that was one part of my problem. The second part was that I was losing data from my tab1 when I swiped to tab3.

I found a workaround but it was not a proper solution. Now, I know the answer: this was because the adapter no longer had the reference to tab1 when tab3 was displayed and when I swiped back to tab1 from tab3, the adapter provided a new instance of tab1 which was then partially populated by the code in the fragment's on Create, but the rest of data (e.g. spinner values etc) were lost.

I have fixed this by setting: viewPager.setOffscreenPageLimit(3); Now the adapter keeps the fragments in memory.

See this for more details: Android how to stop refreshing Fragments on tab change

djevulen
  • 112
  • 1
  • 8
0

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.

samaromku
  • 560
  • 2
  • 7
  • Brilliant! :) Thank you very much for this speedy reply. I can now see that the change of indexes is due to this 'visibility' thing: when tab3 is activated, tab2 is at 0, and tab3 is at 1, then when I swipe to tab2 it's index position is not changed, but tab1 shows up again and is inserted back at 2, then when I swipe to tab1, tab3 disappears leaving just tab2 being at 0 and tab1 being at1. That's it! – djevulen Aug 08 '19 at 15:39