3

I have problem with fragments in ViewPager. In my app I have 4 tabs implemented with FragmentStatePagerAdapter. Here is my SectionsPageAdapter.java

public class SectionsPageAdapter extends FragmentStatePagerAdapter {

private String fragment0 = "";
private Fragment fragAt0;
private FragmentManager manager;
private String[] tabNames;
Context context;


public SectionsPageAdapter(FragmentManager fm, Context ctx, String lastFrag) {
    super(fm);
    context = ctx;
    tabNames = ctx.getResources().getStringArray(R.array.tabs_name);
    this.manager = fm;
    this.listener = new fragmentChangeListener();
    fragment0 = lastFrag;
}


@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return ActivityTypeFragment.newInstance(0);
        case 1:
            return EventsFragment.newInstance();
        case 2:
            return CalendarFragment.newInstance();
        case 3:
            if(fragAt0 == null){

                switch(fragment0){
                    case "Chart":
                        fragAt0 = ChartFragment.newInstance(listener);
                        break;
                    case "Hour":
                        fragAt0 = HourChartFragment.newInstance(listener);
                        break;
                    case "Daily":
                        fragAt0 = DailyChartFragment.newInstance(listener);
                        break;
                    default:
                        fragAt0 = ChartFragment.newInstance(listener);
                }
            }
            return fragAt0;
        default:
            return ActivityTypeFragment.newInstance(0);
    }
}

@Override
public int getCount() {
    return 4;
}

@Nullable
@Override
public CharSequence getPageTitle(int position) {
    return tabNames[position];
}

@Override
public int getItemPosition(@NonNull Object object) {
    if (object instanceof ChartFragment && fragAt0 instanceof DailyChartFragment
        || object instanceof HourChartFragment && fragAt0 instanceof DailyChartFragment)
    {
        return POSITION_NONE;
    }
    else if (object instanceof DailyChartFragment && fragAt0 instanceof ChartFragment
            || object instanceof HourChartFragment && fragAt0 instanceof ChartFragment){
        return POSITION_NONE;
    }
    else if (object instanceof HourChartFragment && fragAt0 instanceof ChartFragment)
    {
        return POSITION_NONE;
    }
    else if (object instanceof ChartFragment && fragAt0 instanceof HourChartFragment
            || object instanceof DailyChartFragment && fragAt0 instanceof HourChartFragment)
    {
        return POSITION_NONE;
    }

    return POSITION_NONE;
}

public interface nextFragmentListener {
    void fragment0Changed(String newFragmentIdentification, FragmentManager fragmentManager);
}
private nextFragmentListener listener;

private final class fragmentChangeListener implements nextFragmentListener {


    @Override
    public void fragment0Changed(String fragment, FragmentManager fm) {
        fragment0 = fragment;

        manager.beginTransaction().remove(fragAt0).commitNow();
        switch (fragment) {
            case "Chart":
                fragAt0 = ChartFragment.newInstance(listener);

                break;
            case "Daily":
                fragAt0 = DailyChartFragment.newInstance(listener);
                break;
            case "Hour":
                fragAt0 = HourChartFragment.newInstance(listener);
                break;
        }
        SectionsPageAdapter.this.notifyDataSetChanged();
    }
}

public String getFragmentName()
{
    return fragment0;
}

}

So in last tab I have a fragment - ChartFragment, where I have Spinner with options to select another chart. And when user selects another chart, this another chart reloads in this tab by calling method fragment0Changed that I have in my SectionsPageAdapter Class. I found this solution in https://stackoverflow.com/a/27304530/1573414.

Here is my function onItemSelected which I have in every fragment that I want to be appeared in Charts tab

@Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        if (view != null) {
            String selectedMenu = ((TextView) view).getText().toString();
            if (selectedMenu == getString(R.string.best_appealing_chart)) {
                LoadBestAppealingChart();
            } else if (selectedMenu == getString(R.string.hour_chart)) {
                chartListener.fragment0Changed("Hour", getActivity().getSupportFragmentManager());
            }
            else
            {
                chartListener.fragment0Changed("Daily", getActivity().getSupportFragmentManager());
            }
        }
        else {
            LoadBestAppealingChart();
        }
    }

So, everything works fine until I rotate device, after that selecting another chart does not work. App throwing the exception:

java.lang.IllegalStateException: Fragment host has been destroyed 
at android.support.v4.app.FragmentManagerImpl.ensureExecReady(FragmentManager.java:2183)
        at android.support.v4.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:2211)
        at android.support.v4.app.BackStackRecord.commitNow(BackStackRecord.java:643).

I’ve read the article https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html,but in my case I have nor asynchronus methods where I would replace a fragment, nor I call it in Activity lifecycle methods. I make a fragment transaction in a onSelectionChanged method of my spinner. What I tried: using commitNowAllowingStateLoss() do not make things better, it just didn’t throw an exception, fragments still do not reloading; passing getSupportFragmentManager() parameter to fragment0Changed method doesn’t help either, but doing this does not throw any exception even if i use simple commitNow() method. But what helps is when after rotation I choose first or second tab of my app then go to my fourth tab(Charts tab), then I can switch(reload) my fragments without any problem. Interesting thing that switching to third tab(Calendar tab) does not help either. After switching to third tab and returning to fourth tab switching fragments still do not reloaded.

AnteGemini
  • 92
  • 1
  • 8
  • You should never hold a reference to any Fragment instances inside a FragmentPagerAdapter see #3 https://proandroiddev.com/the-seven-actually-10-cardinal-sins-of-android-development-491d2f64c8e0 – EpicPandaForce Jan 10 '20 at 07:50

2 Answers2

1

I think that its a common mistake on Android to communicate with Fragment passing listener. Listener on Fragment must be implemented by the Activity or a child Fragment. So you have to make your Activity or Fragment implements NextFragmentListener... Second, never make a reference to a Fragment on ViewPager, you are leaking the old fragment0

Anis BEN NSIR
  • 2,537
  • 18
  • 28
0

public class SubAdapter extends FragmentStatePagerAdapter {

/* just pass the behavior in super() call so every time resume method will call so that you can reload any thing you want But you should use subclass extending */

private String tile_list[];

public SubAdapter (FragmentManager fm, String[] tile_list)
{

    super(fm,FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
    this.tile_list = tile_list;
}

@Override
public Fragment getItem(int position)
{
    Fragment fragment;
    switch (position)
    {
        case 0:
            fragment=new new_fragment();
            break;

          default:
              fragment=new new_fragment();

    }
    return fragment;
}

@Override
public int getCount()
{
    return tile_list.length;
}

@Nullable
@Override
public CharSequence getPageTitle(int position)
{
    return tile_list[position];
}

}