onResume() not called on ViewPager fragment when using custom Loader
Asked Answered
H

2

5

Short version:

I have a fragment that maintains a ViewPager for displaying two other fragments, let's call them FragmentOne and FragmentTwo. When starting the app FragmentOne is visible and FragmentTwo is off-screen, becoming visible only as one swipes the view to the left.

Normally onStart() and onResume() get invoked immediately for both fragments as soon as the app gets started.

The problem I have is when FragmentOne starts a custom Loader then onResume() does not get called on FragmentTwo until it becomes fully visible.

Screenshot showing the problem

Questions:

Is this a problem with my code or a bug in the Android Support Library? (The problem did not occur with revision 12 of the library, it started with revision 13.)

If it's a bug in revisons 13 and 18, is there a workaround?

Is there something wrong with my custom Loader?

Long version:

I have built a sample application that demonstrates the problem. I have tried to reduce the code to the bare minimum but it's still a lot so please bear with me.

I have a MainActivity that loads a MainFragment which creates a ViewPager. It is important for my app that the ViewPager is maintained by a Fragment instead of an Activity.

MainFragment creates a FragmentPagerAdapter that in turn creates the fragments FragmentOne and FragmentTwo.

Let's start with the interesting bit, the two fragments:

FragmentOne is a ListFragment that uses a custom Loader to load the content:

public class FragmentOne extends ListFragment implements LoaderCallbacks<List<String>> {
    private ArrayAdapter<String> adapter;

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

        adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1);
        setListAdapter(adapter);

        setEmptyText("Empty");
    }

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

        // initializing the loader seems to cause the problem!
        getLoaderManager().initLoader(0, null, this);
    }

    @Override
    public Loader<List<String>> onCreateLoader(int id, Bundle args) {
        return new MyLoader(getActivity());
    }

    @Override
    public void onLoadFinished(Loader<List<String>> loader, List<String> data) {
        adapter.clear();
        adapter.addAll(data);
    }

    @Override
    public void onLoaderReset(Loader<List<String>> loader) {
        adapter.clear();
    }

    public static class MyLoader extends AsyncTaskLoader<List<String>> {
        public MyLoader(Context context) {
            super(context);
        }

        @Override
        protected void onStartLoading() {
            forceLoad();
        }

        @Override
        public List<String> loadInBackground() {
            return Arrays.asList("- - - - - - - - - - - - - - - - - - - foo",
                    "- - - - - - - - - - - - - - - - - - - bar",
                    "- - - - - - - - - - - - - - - - - - - baz");
        }
    }
}

It is that Loader that seems to cause the problem. Commenting out the initLoader line makes the fragment life-cycle work as expected again.

FragmentTwo changes its content based on whether onResume() has been invoked or not:

public class FragmentTwo extends Fragment {
    private TextView text;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        text = new TextView(container.getContext());
        text.setText("onCreateView() called");
        return text;
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.i("Fragment2", "onResume() called");
        text.setText("onResume() called");
    }
}

And here is the boring rest of the code.

MainActivity:

public class MainActivity extends FragmentActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        Fragment fragment = new MainFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.container, fragment).commit();
    }
}

Layout activity_main:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

MainFragment:

public class MainFragment extends Fragment {
    private ViewPager viewPager;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View layout = inflater.inflate(R.layout.frag_master, container, false);
        viewPager = (ViewPager) layout.findViewById(R.id.view_pager);
        return layout;
    }

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

        viewPager.setAdapter(new MyPagerAdapter(getChildFragmentManager()));
    }

    private static final class MyPagerAdapter extends FragmentPagerAdapter {
        public MyPagerAdapter(FragmentManager fragmentManager) {
            super(fragmentManager);
        }

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

        @Override
        public Fragment getItem(int position) {
            if (position == 0)
                return new FragmentOne();
            else
                return new FragmentTwo();
        }
    }
}

Layout frag_master:

<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/view_pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
Herminahermine answered 18/8, 2013 at 17:45 Comment(0)
L
11

It appears to be a bug in support library. The change below solves the issue.

// FragmentOne.java

@Override
public void onResume() {
    super.onResume();
    Handler handler = getActivity().getWindow().getDecorView().getHandler();
    handler.post(new Runnable() {
        @Override public void run() {
            // initialize the loader here!
            getLoaderManager().initLoader(0, null, FragmentOne.this);
        }
    });
}
Ligature answered 18/8, 2013 at 18:50 Comment(5)
I'm having exactly same issue as you are, you solution works but with performance - delay price. How did you find solution ?Hjerpe
why do you post it into the handler?Cedric
@Cedric This is just a workaround, but it is safe to use. Here is my thoughts. We see that calling initLoader() in first Fragment prevents from calling onResume() on other fragments (most probably due to a bug in the library). In there is no such call, all onResume() methods get called in a single execution frame. Logically, if we want framework to call onResume() methods in all Fragments, we must avoid calling initLoader(). Logically, to initialize loader immediately after onResume() we need to do it in the next execution frame. This is where handler.post() helps a lot.Ligature
@beworker I was losing my mind over this one, nice workaround and explanation.Broth
I notice that this problem only happens if we use childFragmentManager, it does not if we pass the fragmentManager into ViewPager adapter creation. But use fragmentManager instead may lead to some unexpected behaviors. Anw, difference between these 2 fragment managers remains a mystery to me. Tried tracing support library code all day but no hope! So thanks a lot to this answer!!!Millikan
S
3

Another workaround that worked for me was to use the MainFragment's loader manager:

getParentFragment().getLoaderManager().initLoader(0, null, this);
Silas answered 5/3, 2015 at 17:45 Comment(1)
I was looking a solution for my question https://mcmap.net/q/1922793/-viewpager-setoffscreenpagelimit-not-working-when-using-it-as-nestedfragment-with-getchildfragmentmanager/2277631 when I found a similar one https://mcmap.net/q/1922794/-asynloadertask-and-viewpager/2277631 that led me here. This seems to just work. Did you find any drawback of using parent fragment to init the loader?Vagary

© 2022 - 2024 — McMap. All rights reserved.