Aegis
Aegis

Reputation: 5791

Running two AsyncTaskLoaders in Parallel

I'm trying to create a search function that loads search results into a fragment's list. The problem is that the search does two requests to an API: one for type A and one for type B.

The way I implemented was to create a ViewPager with two fragments, FragmentA and FragmentB. Each calls the API endpoint with search query through a custom Loader extending AsyncTaskLoader, which does the API query and returns the results. This works as excepted; the fragments receive the search results and show them in a List.

The only problem I'm having with this construction is that while both fragments start their loaders with a seperate LOADER_IDs, only FragmentA actually calls loadInBackground() from the AsyncTaskLoader. Only when I swipe to FragmentB in the ViewPager doest it call restartLoader() (and in turn loadInBackground()), executing the API request for the search and returning the result.

The reason I want to run both loaders parallel is because when FragmentB has search results the ViewPager should show FragmentB.

SearchListFragment:

public class SearchListFragment extends Fragment implements LoaderManager.LoaderCallbacks<SearchResults> {

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        getLoaderManager().enableDebugLogging(BuildConfig.DEBUG);
        if (mSearchType == SearchType.ARTIST) {
            getLoaderManager().initLoader(R.id.search_artist_loader, null, this);
        } else {
            getLoaderManager().initLoader(R.id.search_track_loader, null, this);
        }
        BusProvider.getInstance().register(this);
    }

    @Override
    public Loader<ApiResponse> onCreateLoader(int id, Bundle args) {
        int offset = 0;
        String query = null;
        if (args != null) {
            if (args.containsKey("offset")) {
                offset = args.getInt("offset");
            }
            if (args.containsKey(SearchManager.QUERY)) {
                query = args.getString(SearchManager.QUERY);
                mSearchQuery = query;
            }
        }
        Log.d(TAG, "onCreateLoader for " + mSearchType.name());
        Log.d(TAG, "id: " + id);

        SearchLoader loader = new SearchLoader(getActivity(), mSearchType, offset, query);
        return loader;
    }

    @Override
    public void onLoadFinished(Loader<SearchResults> loader, SearchResults data) {

        if (data != null) {
            if (data.mSampleList.isEmpty()) {
                mNoResults.setVisibility(View.VISIBLE);
            } else {
                mAdapter.swapApiResponse(data);
                mListView.setVisibility(View.VISIBLE);
            }
            mLoading.setVisibility(View.GONE);
        } else {
            Exception exception = ((SearchLoader) loader).getError();
            if(exception != null) {
                Log.e(TAG, exception.toString());
                Log.e(TAG, exception.getMessage());
                Log.e(TAG, exception.getLocalizedMessage());
            }
            mNoResults.setVisibility(View.VISIBLE);
            mLoading.setVisibility(View.GONE);
        }
    }

    @Override
    public void onLoaderReset(Loader<ApiResponse> loader) {

    }
}

CustomLoader:

public class SearchLoader extends AsyncTaskLoader<ApiResponse> {

    private static final String TAG = SearchLoader.class.getSimpleName();
    private SearchResults mApiResponse;
    private SearchType mSearchType;
    private int mOffset;
    private String mSearchQuery;
    private Exception error = null;

    public SearchLoader(Context context, SearchType type, int offset, String query) {
        super(context);
        mSearchType = type;
        mOffset = offset;
        mSearchQuery = query;
    }

    @Override
    public ApiResponse loadInBackground() {
        try {
            return tryLoadInBackground();
        } catch (Exception e) {

            error = e;
            return null;
        }
    }

    public ApiResponse tryLoadInBackground() throws Exception {
        if (mSearchQuery != null) {
            Map<String, String> parameters = Utils.parametersMap("q:" + mSearchQuery, "offset:" + String.valueOf(mOffset));
            if (mSearchType == SearchType.ARTIST) {
                return API.getRestAdapter().searchTypeA(parameters);
            } else {
                return API.getRestAdapter().searchTypeB(parameters);
            }
        }
        return null;
    }

    @Override
    protected void onStartLoading() {
        Log.d(TAG, "onStartLoading for " + mSearchType.name());
        if (mApiResponse != null) {
            deliverResult(mApiResponse);
        }
        if (takeContentChanged() || mApiResponse == null) {
            forceLoad();
        }
    }


    @Override
    protected void onStopLoading() {
        cancelLoad();
    }

    @Override
    public void onCanceled(ApiResponse data) {
        // Attempt to cancel the current asynchronous load.
        super.onCanceled(data);

        onReleaseResources(data);
    }

    @Override
    protected void onReset() {
        // Ensure the loader has been stopped.
        onStopLoading();


        // At this point we can release the resources associated with 'apps' if needed
        if (mApiResponse != null) {
            onReleaseResources(mApiResponse);
            mApiResponse = null;
        }
    }

    @Override
    public void deliverResult(ApiResponse data) {
        if (isReset()) {
            // An async query came in while the loader is stopped. We don't need the result
            if (data != null) {
                onReleaseResources(data);
            }
            return;
        }
        SearchResults oldData = mApiResponse;
        mApiResponse = data;

        if (isStarted()) {
            // If the loader is currently started, we can immediately deliver a result
            super.deliverResult(mApiResponse);
        }

        // At this point we can release the resources associated with 'oldApps' if needed;
        // now that the new result is delivered we know that it is no longer in use
        if (oldData != null && oldData != mApiResponse) {
            onReleaseResources(oldData);
        }
    }

    /**
     * Helper function to take care of releasing resources associated with an actively loaded data set
     */
    private void onReleaseResources(ApiResponse data) {
        // For a simple list there is nothing to do
        // but for a Cursor we would close it here
    }

    public Exception getError() {
        return error;
    }
}

Upvotes: 1

Views: 882

Answers (1)

tbellenger
tbellenger

Reputation: 181

Did you read this? It's probably too do with the executor.

http://commonsware.com/blog/2012/04/20/asynctask-threading-regression-confirmed.html

Upvotes: 1

Related Questions