Reputation: 5791
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
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