Last active
December 18, 2018 18:23
-
-
Save 1zaman/dab740c55810de693054 to your computer and use it in GitHub Desktop.
Loader for Retrofit 2.0 API. This uses the framework implementation, but it can be adapted to use the support library implementation without much modification.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.retrofit.loader; | |
import retrofit.Response; | |
/** | |
* A wrapper around the Retrofit {@link Response} that will | |
* throw any loading errors upon retrieval. | |
* | |
* @param <T> The data type that was loaded. | |
*/ | |
public interface ResultHolder<T> { | |
/** | |
* Get the wrapped Response. | |
* | |
* @return The wrapped Response. | |
* @throws Throwable if Retrofit encountered an error | |
* while performing the HTTP request. | |
*/ | |
Response<T> get() throws Throwable; | |
class ResponseHolder<T> implements ResultHolder<T> { | |
private final Response<T> response; | |
ResponseHolder(Response<T> response) { | |
this.response = response; | |
} | |
@Override | |
public Response<T> get() { | |
return response; | |
} | |
@Override | |
public boolean equals(Object obj) { | |
if (!(obj instanceof ResponseHolder)) return false; | |
Object otherResponse = ((ResponseHolder) obj).response; | |
return response == otherResponse || | |
(response != null && response.equals(otherResponse)); | |
} | |
@Override | |
public int hashCode() { | |
return response == null ? 0 : (response.hashCode() + 1); | |
} | |
@Override | |
public String toString() { | |
return "ResponseHolder{response=" + response + '}'; | |
} | |
} | |
class ErrorHolder<T> implements ResultHolder<T> { | |
private final Throwable error; | |
ErrorHolder(Throwable throwable) { | |
this.error = throwable; | |
} | |
@Override | |
public Response<T> get() throws Throwable { | |
throw error; | |
} | |
@Override | |
public boolean equals(Object obj) { | |
return obj instanceof ErrorHolder && | |
error.equals(((ErrorHolder) obj).error); | |
} | |
@Override | |
public int hashCode() { | |
return error.hashCode() + 1; | |
} | |
@Override | |
public String toString() { | |
return "ErrorHolder{error=" + error + '}'; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* This class is based on AsyncTaskLoader and CursorLoader from the AOSP. | |
* | |
* Copyright (C) 2010 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.example.retrofit.loader; | |
import android.app.LoaderManager; | |
import android.app.LoaderManager.LoaderCallbacks; | |
import android.content.Context; | |
import android.content.Loader; | |
import android.os.Bundle; | |
import java.io.FileDescriptor; | |
import java.io.PrintWriter; | |
import retrofit.Call; | |
import retrofit.Callback; | |
import retrofit.Response; | |
/** | |
* Loader implementation for Retrofit 2.0 Call API. | |
* | |
* @param <T> The data type to be loaded. | |
*/ | |
public class RetrofitLoader<T> extends Loader<ResultHolder<T>> { | |
/** | |
* Load the provided {@link Call} using the {@link LoaderManager}, | |
* or deliver the result if it has already been loaded at the | |
* provided ID. | |
* | |
* @param <T> The data type to be loaded. | |
* @param context The Context to provide to the RetrofitLoader. | |
* @param manager The LoaderManager instance. | |
* @param id The unique identifier to be used for the RetrofitLoader. | |
* @param call The call to be executed. | |
* @param callback The Retrofit callback. | |
*/ | |
public static <T> void load(Context context, LoaderManager manager, | |
int id, Call<T> call, Callback<T> callback) { | |
manager.initLoader(id, null, new LoaderCallbacksDelegator<T>( | |
context, call, callback)); | |
} | |
/** | |
* Reload the provided {@link Call} using the {@link LoaderManager}, | |
* regardless of whether a result has already been loaded or is | |
* currently loading. | |
* | |
* @param <T> The data type to be loaded. | |
* @param context The Context to provide to the RetrofitLoader. | |
* @param manager The LoaderManager instance. | |
* @param id The unique identifier to be used for the RetrofitLoader. | |
* @param call The call to be executed. | |
* @param callback The Retrofit callback. | |
*/ | |
public static <T> void reload(Context context, LoaderManager manager, | |
int id, Call<T> call, Callback<T> callback) { | |
manager.restartLoader(id, null, new LoaderCallbacksDelegator<T>( | |
context, call, callback)); | |
} | |
private static class LoaderCallbacksDelegator<T> | |
implements LoaderCallbacks<ResultHolder<T>> { | |
private final Context context; | |
private final Call<T> call; | |
private final Callback<T> callback; | |
LoaderCallbacksDelegator(Context context, | |
Call<T> call, Callback<T> callback) { | |
this.context = context; | |
this.call = call; | |
this.callback = callback; | |
} | |
@Override | |
public Loader<ResultHolder<T>> onCreateLoader( | |
int id, Bundle args) { | |
return new RetrofitLoader<T>(context, call); | |
} | |
@Override | |
public void onLoadFinished(Loader<ResultHolder<T>> loader, | |
ResultHolder<T> resultHolder) { | |
Response<T> response; | |
try { | |
response = resultHolder.get(); | |
} catch (Throwable t) { | |
callback.onFailure(t); | |
return; | |
} | |
callback.onResponse(response); | |
} | |
@Override | |
public void onLoaderReset(Loader<ResultHolder<T>> loader) {} | |
} | |
private final Call<T> call; | |
private Call<T> currentCall, cancellingCall; | |
private ResultHolder<T> result; | |
public RetrofitLoader(Context context, Call<T> call) { | |
super(context); | |
this.call = call; | |
} | |
@Override | |
protected void onStartLoading() { | |
if (result != null) { | |
deliverResult(result); | |
} else { | |
forceLoad(); | |
} | |
} | |
@Override | |
protected void onStopLoading() { | |
// Cancel the current call. | |
cancelLoad(); | |
} | |
@Override | |
protected void onReset() { | |
super.onReset(); | |
// Ensure the loader is stopped | |
onStopLoading(); | |
result = null; | |
} | |
@Override | |
public void deliverResult(ResultHolder<T> result) { | |
if (isReset()) { | |
// The loader was reset while stopped | |
return; | |
} | |
this.result = result; | |
if (isStarted()) { | |
super.deliverResult(result); | |
} | |
} | |
@Override | |
protected void onForceLoad() { | |
super.onForceLoad(); | |
cancelLoad(); | |
currentCall = call.clone(); | |
if (cancellingCall == null) { | |
currentCall.enqueue(new ResultHandler()); | |
} | |
} | |
@Override | |
protected boolean onCancelLoad() { | |
if (currentCall == null) { | |
return false; | |
} | |
if (cancellingCall != null) { | |
// There was a pending call already waiting for a previous | |
// one being canceled; just drop it. | |
currentCall = null; | |
return false; | |
} | |
currentCall.cancel(); | |
cancellingCall = currentCall; | |
currentCall = null; | |
return true; | |
} | |
private class ResultHandler implements Callback<T> { | |
private final Call<T> call; | |
ResultHandler() { | |
call = currentCall; | |
} | |
@Override | |
public void onResponse(Response<T> response) { | |
onResult(new ResultHolder.ResponseHolder<T>(response)); | |
} | |
@Override | |
public void onFailure(Throwable t) { | |
onResult(new ResultHolder.ErrorHolder<T>(t)); | |
} | |
private void onResult(ResultHolder<T> result) { | |
if (currentCall != call) { | |
if (cancellingCall == call) { | |
cancellingCall = null; | |
deliverCancellation(); | |
if (currentCall != null) { | |
currentCall.enqueue(new ResultHandler()); | |
} | |
} | |
} else if (!isAbandoned()) { | |
currentCall = null; | |
deliverResult(result); | |
} | |
} | |
} | |
@Override | |
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { | |
super.dump(prefix, fd, writer, args); | |
writer.print(prefix); writer.print("call="); writer.println(call); | |
if (currentCall != null) { | |
writer.print(prefix); writer.print("currentCall="); writer.print(currentCall); | |
} | |
if (cancellingCall != null) { | |
writer.print(prefix); writer.print("cancellingCall="); writer.print(cancellingCall); | |
} | |
if (result != null) { | |
writer.print(prefix); writer.print("result="); writer.println(result); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I had subtle bugs (
onLoadFinished()
not called after screen rotation) on the Loaders framework implementation (tested on Android 5.1), so I'd advise you to update this gist to use the support library implementation.