Skip to content

Instantly share code, notes, and snippets.

@mhewedy
Last active June 14, 2021 03:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mhewedy/e297ff80692d54685cb212d308e81de4 to your computer and use it in GitHub Desktop.
Save mhewedy/e297ff80692d54685cb212d308e81de4 to your computer and use it in GitHub Desktop.
Retrofit Promise-like (with then/error funcs) Call Adapter
import android.os.Handler;
import android.os.Looper;
import com.google.gson.Gson;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import retrofit2.Call;
import retrofit2.CallAdapter;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
/**
* Promise-based Retrofit call adapter. <p>
* Example:
* <pre>
* messageClient.getLatest()
* .then(this::onGetLatestSuccess)
* .error(this::onFailure);
* </pre>
*
* @param <R>
*/
public final class Promise<R> {
private static final Gson gson = new Gson();
private static final Executor mainThreadExecutor = new MainThreadExecutor();
private final Call<R> delegate;
private Consumer<R> respFunc;
private Consumer<ApiError> errFunc;
private final Callback<R> callback = new Callback<R>() {
@Override
public void onResponse(Call<R> call, Response<R> response) {
if (response.isSuccessful()) {
mainThreadExecutor.execute(() -> respFunc.accept(response.body()));
} else {
if (errFunc == null) {
return;
}
ApiError apiError;
if (response.errorBody() != null) {
apiError = gson.fromJson(response.errorBody().charStream(), ApiError.class);
} else {
apiError = gson.fromJson(response.message(), ApiError.class);
}
if (apiError == null) {
apiError = new ApiError();
apiError.message = "Error occurred";
}
ApiError finalApiError = apiError;
mainThreadExecutor.execute(() -> errFunc.accept(finalApiError));
}
}
@Override
public void onFailure(Call<R> call, Throwable t) {
if (errFunc == null) {
return;
}
ApiError apiError = new ApiError();
apiError.message = t.getMessage();
mainThreadExecutor.execute(() -> errFunc.accept(apiError));
}
};
private Promise(Call<R> delegate) {
this.delegate = delegate;
}
public Error then(Consumer<R> respFunc) {
this.respFunc = respFunc;
this.delegate.enqueue(this.callback);
return new Error();
}
public class Error {
public void error(Consumer<ApiError> errFunc) {
Promise.this.errFunc = errFunc;
}
}
// ---- setup classes ---------------
public static class PromiseCallAdapterFactory extends CallAdapter.Factory {
public static final PromiseCallAdapterFactory INSTANCE = new PromiseCallAdapterFactory();
@Override
public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
Class<?> rawType = getRawType(returnType);
if (rawType == Promise.class && returnType instanceof ParameterizedType) {
Type callReturnType = getParameterUpperBound(0, (ParameterizedType) returnType);
return new CallAdapter() {
@Override
public Type responseType() {
return callReturnType;
}
@Override
public Object adapt(Call call) {
return new Promise<>(call);
}
};
}
return null;
}
}
private static class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable r) {
handler.post(r);
}
}
// this is the object returned from the api in case of error, change to your own
public static class ApiError {
public String message;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment