Skip to content

Instantly share code, notes, and snippets.

@Nilzor
Last active February 15, 2016 11:35
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 Nilzor/234c2374f6f0d93bcdf3 to your computer and use it in GitHub Desktop.
Save Nilzor/234c2374f6f0d93bcdf3 to your computer and use it in GitHub Desktop.
A Retrofit 1.x HTTP Client using Ion and its AndroidAsync library.
/*
* This class is an adapter for Retrofit allowing you to use ion/andorid-async with Retrofix 1.x.
* Since retrofit is inherently synchronous under the hood, adding this adapter unfortunately does not
* yield a smaller thread footprint. -Notice the .get() blocking call in the code below.
* So why would you use this? My use case is that we rely on third party libraries that requre Retrofit.
* To get all our HTTP comms to go through the same logic we use this adapter.
* That gives us zero dependencies to apache-http-client or okhttp.
*
* ion home: https://github.com/koush/ion
*/
import android.content.Context;
import com.koushikdutta.async.http.Headers;
import com.koushikdutta.ion.HeadersResponse;
import com.koushikdutta.ion.Ion;
import com.koushikdutta.ion.builder.Builders;
import com.koushikdutta.ion.future.ResponseFuture;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import retrofit.client.Client;
import retrofit.client.Header;
import retrofit.client.Request;
import retrofit.client.Response;
import retrofit.mime.TypedInput;
import retrofit.mime.TypedOutput;
/**
* A Retrofit HTTP Client using Ion and its AndroidAsync library.
*/
public class RetrofitIonClient implements Client {
private static final String HEADER_CONTENT_LENGTH = "Content-Length";
private static final String HEADER_CONTENT_TYPE = "Content-Type";
private Ion mIon;
private Context mContext;
public RetrofitIonClient(Context context) {
this(context, null);
}
public RetrofitIonClient(Context context, Ion ionInstance) {
if (context != context.getApplicationContext()) {
throw new IllegalArgumentException("Context must be appContext to avoid activitiy leaks");
}
mContext = context;
mIon = ionInstance;
if (mIon == null) {
mIon = Ion.getDefault(context);
}
}
@Override
public Response execute(Request request) throws IOException {
try {
Map<String, List<String>> headers = convertToIonHeaders(request.getHeaders());
String url = request.getUrl();
Builders.Any.B builder = mIon.build(mContext)
.load(request.getMethod(), url)
.addHeaders(headers);
ResponseFuture<InputStream> future = setPostData(builder, request.getBody()).asInputStream();
// Execute
com.koushikdutta.ion.Response<InputStream> ionResponse = future.withResponse().get();
// Parse response
HeadersResponse ionHeadersObj = ionResponse.getHeaders();
TypedInput retroBody = createTypedInput(ionResponse);
ArrayList<Header> retroHeaders = convertToRetrofitHeaders(ionHeadersObj.getHeaders());
Response retroResponse = new Response(url, ionHeadersObj.code(), ionHeadersObj.message(), retroHeaders, retroBody);
return retroResponse;
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted", e);
} catch (ExecutionException e) {
throw new RuntimeException("Execution failed", e);
}
}
private TypedInput createTypedInput(com.koushikdutta.ion.Response<InputStream> ionResponse) {
Headers headers = ionResponse.getHeaders().getHeaders();
return new TypedInput() {
@Override
public String mimeType() {
return headers.get(HEADER_CONTENT_TYPE);
}
@Override
public long length() {
String length = headers.get(HEADER_CONTENT_LENGTH);
try {
return Long.parseLong(length);
} catch (Exception ignored) {
}
return 0;
}
@Override
public InputStream in() throws IOException {
return ionResponse.getResult();
}
};
}
private Builders.Any.F setPostData(Builders.Any.B builder, TypedOutput typedOutput) throws IOException {
Builders.Any.F retVal = builder;
if (typedOutput != null) {
// This method of passing on the POST body will result in a buffer at the full size of the post body being allocated
// Consider implementing with use of threading instead, where typedOuptut.write is done on a separate thread.
// What gives the best user experience?
PipedOutputStream outStream = new PipedOutputStream();
PipedInputStream inStream = new PipedInputStream(outStream, (int) typedOutput.length());
retVal = builder.setStreamBody(inStream, (int) typedOutput.length());
builder.setHeader(HEADER_CONTENT_TYPE, typedOutput.mimeType());
typedOutput.writeTo(outStream);
}
return retVal;
}
private HashMap<String, List<String>> convertToIonHeaders(List<Header> headers) {
HashMap<String, List<String>> outHeaders = new HashMap<>();
for (Header header: headers) {
ArrayList<String> outValues = new ArrayList<>(1);
outValues.add(header.getValue());
outHeaders.put(header.getName(), outValues);
}
return outHeaders;
}
private ArrayList<Header> convertToRetrofitHeaders(Headers headers) {
ArrayList<Header> retroHeaders = new ArrayList<>();
Set<String> keys = headers.getMultiMap().keySet();
// This method does not support the use case where one header is sent multiple times
// because Retrofit1 doesn't support it
for (String headerKey : keys) {
String value = headers.get(headerKey);
retroHeaders.add(new Header(headerKey, value));
}
return retroHeaders;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment