0

I have an activity (MainActivity) that contains MasterFragment which contains a viewpager with FragmentA and FragmentB in portrait screen orientation.

In landscape mode the viewpager contains only FragmentA on left side of a split screen, with FragmentB on the right side.

So basically FragmentB is moved to the right of the viewpager in landscape mode.

Although FragmentB is only shown once in each rotation, two instances are created at the same time after rotation.

The problem is that FragmentB is in reality a map, and I need to prevent 2 instances to be created at the same time. I need the first instance to be destroyed before the next instance is created.

What happens is the FragmentStateManager recreates FragmentB when calling setContentView in MainActivity.

How do I prevent that?

One solution would be to use super.onCreate(null) in MainActivity, but that is clearly an overkill.

How can I prevent recreating fragments in ViewPager2?

Another solution would be to use the recreated fragment instance and move it from the viewpager to the framlayout and vice versa. How can I move it?

MasterFragment.java

public class MasterFragment extends Fragment
{
    NewPagerAdapter mSectionsPagerAdapter;
    ViewPager2 mViewPager;
    boolean mSplitView;

    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
    {
        View view = inflater.inflate(R.layout.masterfragment, container, false);

        if (isLandScape())
        {
            mSplitView = true;
            getChildFragmentManager().beginTransaction().replace(R.id.container, new FragmentB(), FragmentB.TAG).commit();
        }
        else if (isLandScape())
        {
            LinearLayout masterlayout = view.findViewById(R.id.masterlayout);
            masterlayout.removeViewAt(1);
        }

        mViewPager = view.findViewById(R.id.pager);
        mSectionsPagerAdapter = new NewPagerAdapter(getChildFragmentManager(), getLifecycle());

        mViewPager.setOffscreenPageLimit(7);
        mViewPager.setAdapter(mSectionsPagerAdapter);

        return view;
    }

    public boolean isLandScape()
    {
        int orientation = getResources().getConfiguration().orientation;
        return orientation == Configuration.ORIENTATION_LANDSCAPE;
    }

    public boolean backOnePage()
    {
        if(mViewPager == null)
            return false;

        int page = mViewPager.getCurrentItem();

        if(page > 0)
        {
            mViewPager.setCurrentItem(page - 1);
            return true;
        }

        return false;
    }

    public void viewFragmentB()
    {
        if(!mSplitView)
            mViewPager.setCurrentItem(1);
    }

    public class NewPagerAdapter extends FragmentStateAdapter
    {
        public NewPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle)
        {
            super(fragmentManager, lifecycle);
        }

        @NonNull
        @Override
        public Fragment createFragment(int position)
        {
            if(position == 0)
                return new FragmentA();

            return new FragmentB();
        }

        @Override
        public int getItemCount()
        {
            return mSplitView ? 1 : 2;
        }
    }
}

masterfragment.xml (Portrait)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ff000000"
        android:orientation="vertical">

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

masterfragment.xml (Landscape)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/masterlayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentEnd="true"
    android:background="#ff000000">

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

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="0dp"
        android:layout_height="fill_parent"
        android:layout_weight="0.5"/>

</LinearLayout>

MainActivity.java

public class MainActivity extends FragmentActivity
{
    MasterFragment mMasterFragment;

    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);

        setContentView(R.layout.main);

        Fragment fragment = savedInstanceState != null
                ? getSupportFragmentManager().getFragment(savedInstanceState, "MasterFragment")
                : null;

        mMasterFragment = fragment instanceof MasterFragment
                ? (MasterFragment)fragment
                : (MasterFragment)getSupportFragmentManager().findFragmentById(R.id.masterfragment);
    }
    
    @Override
    public void onBackPressed() 
    {
        if(mMasterFragment != null && mMasterFragment.backOnePage())
            return;

        super.finish();
    }
}

FragmentA

public class FragmentA extends Fragment
{   
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
    {
        View view = inflater.inflate(R.layout.fragment_home, container, false);

        view.findViewById(R.id.title).setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                MasterFragment.getInstance().viewFragmentB();
            }
        });

        return view;
    }
}

FragmentB

public class FragmentB extends Fragment
{
    public static String TAG = "OrderFragment";
    static int COUNTER;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState)
    {
        View view = inflater.inflate(R.layout.fragment_crew, container, false);

        COUNTER++; // COUNTER BECOMES 2

        return view;
    }

    @Override
    public void onDestroy()
    {
        COUNTER--;

        super.onDestroy();
    }
}

main.xml

<fragment xmlns:android="http://schemas.android.com/apk/res/android" 
    android:name="com.mobile.MasterFragment"
    android:id="@+id/masterfragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

3 Answers3

0

You need to use setRetainInstance in fragment that you want to retain over Activity configuration change.

Source

Edit

Thanks @martin for pointing that setRetainInstance is deprecated.

The requirement to prevent fragment recreation over configuration change is unachievable as per my understanding. I would suggest to maintain the fragment state via view model instance as per Android Doc's suggestion

hardik9850
  • 581
  • 1
  • 9
  • 26
  • 1
    `setRetainInstance` has been marked as deprecated, this is not a good idea. (Deprecated in API level 28) – Martin Marconcini Sep 17 '21 at 09:25
  • setRetainInstance has been deprecated. I am not trying to retain the same instance, but trying to prevent creating 2 instances at the same time (onCreateView is called 2 times without onDestroy in between) – user3115340 Sep 17 '21 at 09:25
  • FragmentA is only created once at a time, although it appears in both portrait and landscape in the viewpager. – user3115340 Sep 17 '21 at 09:27
  • Reason is, during landscape rotation you are adding a new FragmentB instance in FrameLayout container to show it. At the same time your MasterFragment is recreated again implicitly invoking FragmentB twice. – hardik9850 Sep 17 '21 at 10:51
  • How can I prevent recreation of a given fragment or fragment instance? – user3115340 Sep 17 '21 at 10:57
  • Similar question here, but I just want to prevent automatic recreation of a specific fragment https://stackoverflow.com/questions/15519214/prevent-fragment-recovery-in-android – user3115340 Sep 17 '21 at 10:58
  • 1
    I'm afraid we cannot prevent recreation of a certain fragment. I'd rather save the Fragment state in viewmodel instance and restore it upon recreation. I agree with @Martin + This https://android-review.googlesource.com/c/platform/frameworks/support/+/1159084/ – hardik9850 Sep 17 '21 at 11:03
  • The problem is not the state (there is no state), but creating 2 instances of this fragment causes problem. I need to prevent that. – user3115340 Sep 17 '21 at 12:48
0

I think your problem is that you're always doing new FragmentB() or new Fragment...() instead of checking if it's already in the fragmentManager.

You have to do something like (please excuse my kotlin pseudocode)


var fragmentB = fragmentManager.findFragmentByTag("FragB")

if fragmentB == null { 
   fragmentB = // create new instance
} 

fragmentManager.replace(..., fragmentB, "FragB") //use the same tag you'll use later to search for it



Martin Marconcini
  • 26,875
  • 19
  • 106
  • 144
  • the 'FragmentStateAdapter' used by the ViewPager adds FragmentB to FragmentManager with tag "f0", "f1",, based on its position in the viewpager. I tried to find that instance when rotating to landscape and use that instead of new instance, but it the fragment does appear (the right half pane is black). – user3115340 Sep 17 '21 at 09:52
  • I mean "does not appear" – user3115340 Sep 17 '21 at 10:04
  • I would think the solution would be rather to remove fragment from fragmentmanager before another instance is created – user3115340 Sep 17 '21 at 10:06
  • It's been a while since I used a VPager w/those adapters, so is there no way to override the Tag? – Martin Marconcini Sep 17 '21 at 10:12
  • That would be very hacky. I think it is not possible to reuse the fragment, I would rather like to make sure the FragmentStateManager won't recreate FragmentB – user3115340 Sep 17 '21 at 10:30
0

Seems I need to answer my own question.

As some mentioned this is not directly supported by the Android platform.

Some possibilities are:

  1. super.onCreate(null) in MainActivity (which will disable all state restore)

  2. Write custom viewpager/adapter that omit the fragment in saving state (a lot of work)

  3. After rotation, move fragment from/to Viewpager to/from Framelayout, but this will require customer viewpager/adapter that allows removing fragment view without destroying it (see moving view Android Fragment - move from one View to another?), which is presumably a lot of work