Search code examples
androidandroid-viewpagerandroid-listfragmentandroid-loader

ListFragment sometimes skips code in onLoadFinished() method of Loader


I have a ListFragment which fetches data from the net using a Loader. I use a new instance of this ListFragment in every page of my ViewPager. It works perfectly, but when I use TabLayout or moves pages quickly, the Fragment keeps loading and does not display the data in the ListView.

When I checked using log messages, I found that the ListFragment skips some lines of code in the onLoadFinished() method. It does not make the ProgressBar invisible. It does add items to Adapter, but it is not being displayed in the ListView. This problem also happens in the first page of the ViewPager.

Is there any specific rule to be followed when using ListFragments in a ViewPager?

Here is the ListFragment class. If you look at the onLoadFinished() method, you can see the lines causing problem:

public class ListViewFragment extends ListFragment
    implements LoaderManager.LoaderCallbacks<List<GameNews>> {

    public static ListViewFragment newInstance(String url) {
        Log.d("ListViewFragment", "newInstance created");
        ListViewFragment f = new ListViewFragment();

        // Supply url input as an argument.
        Bundle args = new Bundle();
        args.putString("url", url);
        f.setArguments(args);
        return f;
    }

    List<GameNews> TotalNews;
    ListView gameListView;
    LinearLayout emptyView;
    Button retryButton;
    ListAdapter adapter ;
    private View progressBar;
    final private int game_loader = 0;
    ArrayList<String> urls = new ArrayList<>();
    String mUrlString;
    int index;

    //LIFE CYCLE OF FRAGMENT
    //------------------------------------------------------------------
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mUrlString = getArguments().getString("url");
        urls.add(mUrlString);
        TotalNews = new ArrayList<GameNews>();
    }

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

        ArrayList<GameNews> gameList = new ArrayList<>();
        adapter = new ListAdapter(getActivity(), gameList);
        return rootView;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        emptyView = (LinearLayout)
            view.findViewById(R.id.no_internet_view);
        progressBar = view.findViewById(R.id.progress_bar);
        retryButton = (Button) view.findViewById(R.id.retry_button);
        gameListView = getListView();
        emptyView.setVisibility(View.INVISIBLE);
    }

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

        //If connected to net start the loader
        if (isConnected()) {
            getActivity().getSupportLoaderManager().restartLoader(game_loader,
                                                                  null,
                                                                  ListViewFragment.this);
        }
    }

    //OVERRIDED METHODS OF LOADERMANAGER
    //---------------------------------------------------------------------
    @Override
    public android.support.v4.content.Loader<List<GameNews>> onCreateLoader(int i, Bundle bundle) {
        AdManager manager =  new AdManager(getActivity());
        return new FragmentLoader(getActivity(), urls, 1000);

    }

    //HERE IS THE PROBLEM PLEASE FOCUS INSIDE THIS METHOD
    //-------------------------------------------------------
    @Override
    public void onLoadFinished(Loader<List<GameNews>> loader, List<GameNews> games) {

        progressBar.setVisibility(View.INVISIBLE); //This line of code is not executed
        adapter.clear();
        TotalNews.addAll(games);
        adapter.addAll(games);//And the listView is not populated
    }
    //-------------------------------------------------------

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

    //REUSABLE METHODS
    //------------------------------------------------------------------
    //Method checks if there is internet
    public boolean isConnected() {
        ConnectivityManager manager = (ConnectivityManager)
            getActivity().getSystemService(CONNECTIVITY_SERVICE);
        NetworkInfo info = manager.getActiveNetworkInfo();
        if (info != null && info.isConnected()) {
            return true;
        }
        else {
            return false;
        }
    }
}

Solution

  • Your Fragment class is using the Activity's LoaderManager:

    getActivity().getSupportLoaderManager().restartLoader(...);
    

    And each instance is using the same ID in its restartLoader() call:

    final private int game_loader = 0;
    

    This means that each Fragment instance was using and restarting the same Loader over and over again, leading to the weird behavior you observed.

    The solution is quite simple: use Fragment's local LoaderManager, instead of the Activity's.

    getLoaderManager().restartLoader(...);
    

    With this, you don't need to worry about changing the ID in each instance, since Loaders are unique to their Fragment, and the Loader will be properly handled over the Fragment's lifetime, which would likely not have been the case when using the Activity's LoaderManager.