Unable to upload video using Retrofit, possible fix? - java

I am trying to upload video to server using Retrofit as multi part form data , And I am using Retrofit version: 2.02
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
This is the code to the API client class:
ApiClient.java
public class ApiClient {
public static final String BASE_URL = "https://api.sproutvideo.com";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
This is the code to API interface:
ApiInterface.java
public interface ApiInterface {
#Multipart
#POST("/v1/videos")
Call<SproutReply> pushVideo (#Header("SproutVideo-Api-Key") String key, #Part("file\"; filename=\"pp.mp4") RequestBody file);
}
And my requirement is to upload the video picked using Intent.ACTION_PICK.
And this is how I invoke the API:
private void makeRequest(Uri uri)
{
try {
String path = uri.toString();
Log.e("PATH", path);
URI ss = new URI(uri.toString());
file = new File(ss);
}
catch (Exception e){}
RequestBody video = RequestBody.create(MediaType.parse("video/mp4"),file);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ApiInterface apiService =
ApiClient.getClient().create(ApiInterface.class);
Call<SproutReply> call = apiService.pushVideo(KEY,video);
call.enqueue(new Callback<SproutReply>()
{
#Override
public void onResponse(Call<SproutReply> call, Response<SproutReply> response)
{
try
{
Log.e("TAG",""+response.body().toString());
}
catch (Exception e)
{
Toast.makeText(getActivity(), "Check data connection", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(Call<SproutReply> call, Throwable t)
{
// Log error here since request failed
Log.e("FAILURE", t.toString());
}
});
}
The call results in a NullPointerException as content == null at this line:
RequestBody video = RequestBody.create(MediaType.parse("video/mp4"),file);
However in this line when I log the Uri( Log.e("PATH", path); ) as String I obtain the value as :
content://com.android.providers.media.documents/document/video%3A75406
And using this Uri I am able to play video in VideoView as well, however in Retrofit it seems to crash, what is possibly causing this and how to fix?
Here is the logcat as well:
java.lang.NullPointerException: content == null
at okhttp3.RequestBody.create(RequestBody.java:103)
at com.example.demovid.UploadFragment.makeRequest(UploadFragment.java:91)
at com.example.demovid.UploadFragment.onEvent(UploadFragment.java:133)
at java.lang.reflect.Method.invoke(Native Method)
at org.greenrobot.eventbus.EventBus.invokeSubscriber(EventBus.java:485)
at org.greenrobot.eventbus.EventBus.postToSubscription(EventBus.java:416)
at org.greenrobot.eventbus.EventBus.postSingleEventForEventType(EventBus.java:397)
at org.greenrobot.eventbus.EventBus.postSingleEvent(EventBus.java:370)
at org.greenrobot.eventbus.EventBus.post(EventBus.java:251)
at com.example.demovid.MainActivity.onActivityResult(MainActivity.java:67)
at android.app.Activity.dispatchActivityResult(Activity.java:6456)
at android.app.ActivityThread.deliverResults(ActivityThread.java:3729)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:3776)
at android.app.ActivityThread.-wrap16(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1412)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5461)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

The issue is that you have a content URI and not a file path. When you pass that to the File constructor, it throws a FileNotFoundException that you silently ignore with
`catch (Exception e){}`
Instead of using a file, you can get a file descriptor to the data using --
ParcelFileDescriptor fd = getActivity().getContentResolver().openFileDescriptor(uri, "r");
and use that to construct your RequestBody. There is no out of the box method to create one from the descriptor, but it is fairly easy to write a routine to do it --
public static RequestBody createBody(final MediaType contentType, final ParcelFileDescriptor fd) {
if (fd == null) throw new NullPointerException("content == null");
return new RequestBody() {
#Override public MediaType contentType() {
return contentType;
}
#Override public long contentLength() {
return fd.getStatSize();
}
#Override public void writeTo(BufferedSink sink) throws IOException {
Source source = null;
try {
source = Okio.source(new ParcelFileDescriptor.AutoCloseInputStream(fd));
sink.writeAll(source);
} finally {
Util.closeQuietly(source);
}
}
};
}
then use it like --
RequestBody video = RequestBody.createBody(MediaType.parse("video/mp4"), fd);
I also noticed that you are hardcoding the media type, you can get the type from the content resolver as well with a call to getType

Related

Convert Java object into JSON array for Retrofit call

I need to send an array of Strings using a Retrofit call. To do that I decided to create an object like this one:
public class SendEmailsList {
ArrayList<String> emails;
public SendEmailsList(ArrayList<String> emails) {
this.emails = emails;
}
}
And my JSON String must be like this:
{
"emails": ["email#server.com","email1#server.com","email2#server.com"]
}
This is the POST method defined in my interface:
#POST("/v2/companies/{companyId}/invite")
Call<ArrayList<String>> inviteMembers(#Path("companyId") String companyId, #Body SendEmailsList emails);
And this is the method that makes the Retrofit call:
public void SendNetworkRequest() {
OkHttpClient.Builder okhttpBuilder = new OkHttpClient.Builder();
okhttpBuilder.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder newRequest = request.newBuilder().header("Authorization", "Bearer " + token);
return chain.proceed(newRequest.build());
}
});
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(BuildConfig.ENDPOINT)
.client(okhttpBuilder.build())
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
CompanyService invite = retrofit.create(CompanyService.class);
Call<ArrayList<String>> call = invite.inviteMembers("5602eb7ce49c9cd70409f206", new SendEmailsList(invitedEmails));
call.enqueue(new Callback<ArrayList<String>>() {
#Override
public void onResponse(Call<ArrayList<String>> call, Response<ArrayList<String>> response) {
System.out.println("Internal Users: " + response.code());
}
#Override
public void onFailure(Call<ArrayList<String>> call, Throwable t) {
// Log error here since request failed
Log.e("Internal Users Activity", t.toString());
}
});
}
But I am getting this error:
12-21 14:36:49.953 27953-27953/com.construct.test E/Internal Users Activity: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
How can I figure out what is going on?

Handling Retrofit status codes without a pojo class

I got the following response from my server: status code 201 Created.
There is no actual response (returned object, etc.), so there is not need to create a POJO class.
So, I don't know how I should handle this status code without creating a POJO class. Is there any option to make write the code without using a POJO class?
Retrofit API has Response class that can encapsulate your response.
As long as you don't want to bother with the response data you can implement your service as:
interface CustomService {
#GET("whatever")
Call<Response<Void>> getAll();
// Or using RxJava:
#GET("whatever")
Single<Response<Void>> getRxAll();
}
Then implement your callback:
private Callback<Response<Void>> responseHandler = new Callback<Response<Void>>() {
#Override
public void onResponse(Call<Response<Void>> call, Response<Response<Void>> response) {
final int code = response.code();
// TODO: Do whatever you want with the response code.
}
#Override
public void onFailure(Call<Response<Void>> call, Throwable t) {
// TODO: Handle failure.
}
}
Or reactive consumer:
private Consumer<Response<Void>> responseRxHandler = new Consumer<Response<Void>>() {
#Override
public void accept(Response<Void> response) throws Exception {
final int responseCode = response.code();
// TODO: Do whatever you want with the response code.
}
};
Debugging result:
You can try the following code.
Can get the response without a POJO class by getting using ResponseBody format and then you can parse it normally like ordinary JSON parsing.
Api Call:
Call<ResponseBody> call = service.callLogin(AppConstants.mApiKey, model_obj);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if(response.code() == 201)
{
JSONObject jobjresponse = null;
try {
jobjresponse = new JSONObject(mResponse.body().string());
String status = jobjresponse.getString("status");
JSONObject result = jobjresponse.getJSONObject("results");
String msg = result.getString(“msg”);
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
Retrofit Interface class:
public interface RetrofitInterface {
#Headers({"Content-Type: application/json", "Cache-Control: max-age=640000"})
#POST("v1/auth/")
public Call<ResponseBody> callLogin(#Query("key") String key, #Body LoginModel body);
public static final Retrofit retrofit = new Retrofit.Builder()
.baseUrl(“base url”)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
Sample Response:
{ "status":"true", "result":{"msg”:”created successfully”} }

Retrofit2 Handle condition when status code 200 but json structure different than datamodel class

I'm using Retrofit2 and RxJava2CallAdapterFactory.
The API I consume returns status code always as 200 and for success and response JSON string the JSON structure is entirely different. Since the status code is always 200 the onResponse() method is called always. Hence, I'm not able to extract error msgs from the JSON in the error condition.
Solution 1:
I use ScalarsConverterFactory to get response String and manually use Gson to parse the response .
How to get response as String using retrofit without using GSON or any other library in android
Problem with this solution: I'm planning to use RxJava2CallAdapterFactory for that the retrofit method should return DataModel Class.
I need to find the best solution for this problem, in way I can keep returning the data model classes from Retrofit method & somehow I identify the error condition from response (identify the response JSON does not match the data model) and then parse the error JSON into a data model.
Retrofit Client
public static Retrofit getClient(String url) {
if (apiClient == null) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(interceptor).build();
apiClient = new Retrofit.Builder()
.baseUrl(url)
/*addCallAdapterFactory for RX Recyclerviews*/
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
/* add ScalarsConverterFactory to get json string as response */
// .addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
// .addConverterFactory(GsonConverterFactory.create(gson))
.client(httpClient)
.build();
}
return apiClient;
}
Method
public static void getLoginAPIResponse(String username, String password, String sourceId, String uuid, final HttpCallback httpCallback) {
baseUrl = AppPreference.getParam(UiUtils.getContext(), SPConstants.BASE_URL, "").toString();
ApiInterface apiService =
ApiClient.getClient(baseUrl).create(ApiInterface.class);
Call<LoginBean> call = apiService.getLoginResponse(queryParams);
call.enqueue(new Callback<LoginBean>() {
#Override
public void onResponse(Call<LoginBean> call, Response<LoginBean> response) {
if (response.body().isObjectNull()) {
httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_FAILURE, 0, null);
return;
}
httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_SUCCESS, response.code(), response.body());
}
#Override
public void onFailure(Call<LoginBean> call, Throwable t) {
// Log error here since request failed
httpCallback.resultCallback(APIConstants.API_APP_VERIFICATION, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_FAILURE, 0, t);
t.printStackTrace();
}
});
}
Interface
#GET("App/login")
Call<LoginBean> getLoginResponse(#QueryMap Map<String, String> queryMap);
PS :
The API cannot change for now, as some other applications are consuming it.
Gson parser does not return a null object instance for me to understand that there is json structure and datamodel mismatch.
RestAdapter is deprecated in Retrofit 2
I'm looking for the best approach to resolve this , preferably avoid manually json parsing and take most advantage of retrofit and RX adapters.
EDIT
Response code 200 hence
response.isSuccessful() == true
response.body() != null is also true as Gson never creates a null instance or throws any exception if there is mismatch of JSON structure
response.errorBody() == null at all times as response sent as input stream from the server.
if (response.isSuccessful() && response.body() != null) {
//control always here as status code 200 for error condition also
}else if(response.errorBody()!=null){
//control never reaches here
}
EDIT 2
SOLUTION
The solution is based on anstaendig answer
I have created a base generic class to further this answer.
Since I have multiple apis and data models I have to create deserilizers for each
BASE API BEAN
public class BaseApiBean<T> {
#Nullable
private T responseBean;
#Nullable
private ErrorBean errorBean;
public BaseApiBean(T responseBean, ErrorBean errorBean) {
this.responseBean = responseBean;
this.errorBean = errorBean;
}
public T getResponseBean() {
return responseBean;
}
public void setResponseBean(T responseBean) {
this.responseBean = responseBean;
}
public ErrorBean getErrorBean() {
return errorBean;
}
public void setErrorBean(ErrorBean errorBean) {
this.errorBean = errorBean;
}
}
BASE DESERIALIZER
public abstract class BaseDeserializer implements JsonDeserializer<BaseApiBean> {
#Override
public BaseApiBean deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
// Get JsonObject
final JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("result")) {
/* {"result":"404"}*/
ErrorBean errorMessage = new Gson().fromJson(jsonObject, ErrorBean.class);
return getResponseBean(errorMessage);
} else {
return getResponseBean(jsonObject);
}
}
public abstract BaseApiBean getResponseBean(ErrorBean errorBean);
public abstract BaseApiBean getResponseBean(JsonObject jsonObject);
}
Custom Deserializer for each API
public class LoginDeserializer extends BaseDeserializer {
#Override
public BaseApiBean getResponseBean(ErrorBean errorBean) {
return new LoginResponse(null, errorBean);
}
#Override
public BaseApiBean getResponseBean(JsonObject jsonObject) {
LoginBean loginBean = (new Gson().fromJson(jsonObject, LoginBean.class));
return new LoginResponse(loginBean, null);
}
}
CUSTOM RESPONSE BEAN
public class LoginResponse extends BaseApiBean<LoginBean> {
public LoginResponse(LoginBean responseBean, ErrorBean errorBean) {
super(responseBean, errorBean);
}
}
CLIENT
public class ApiClient {
private static Retrofit apiClient = null;
private static Retrofit apiClientForFeedBack = null;
private static LoginDeserializer loginDeserializer = new LoginDeserializer();
private static AppVerificationDeserializer appVerificationDeserializer = new AppVerificationDeserializer();
public static Retrofit getClient(String url) {
if (apiClient == null) {
GsonBuilder gsonBuilder=new GsonBuilder();
gsonBuilder.registerTypeAdapter(LoginResponse.class,
loginDeserializer);
gsonBuilder.registerTypeAdapter(AppVerificationResponse.class,
appVerificationDeserializer);
Gson gson= gsonBuilder.create();
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(interceptor)
.retryOnConnectionFailure(true)
.connectTimeout(15, TimeUnit.SECONDS)
.build();
apiClient = new Retrofit.Builder()
.baseUrl(url)
/*addCallAdapterFactory for RX Recyclerviews*/
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
/* add ScalarsConverterFactory to get json string as response */
// .addConverterFactory(ScalarsConverterFactory.create())
// .addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(httpClient)
.build();
}
return apiClient;
}
HANDLE RESPONSE
public static void getLoginAPIResponse(String username, String password, String sourceId, String uuid, final HttpCallback httpCallback) {
baseUrl = AppPreference.getParam(getContext(), SPConstants.MT4_BASE_URL, "").toString();
ApiInterface apiService =
ApiClient.getClient(baseUrl).create(ApiInterface.class);
HashMap<String, String> queryParams = new HashMap<>();
queryParams.put(APIConstants.KEY_EMAIL, sourceId + username.toLowerCase());
queryParams.put(APIConstants.KEY_PASSWORD, Utils.encodePwd(password));
Call<LoginResponse> call = apiService.getLoginResponse(queryParams);
call.enqueue(new Callback<LoginResponse>() {
#Override
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
if (response.body().getResponseBean()==null) {
httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_FAILURE, 0, response.body().getErrorBean());
return;
}
httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_SUCCESS, response.code(), response.body().getResponseBean());
}
#Override
public void onFailure(Call<LoginResponse> call, Throwable t) {
// Log error here since request failed
httpCallback.resultCallback(APIConstants.API_APP_VERIFICATION, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_FAILURE, 0, t);
t.printStackTrace();
}
});
}
So you have two different successful (status code 200) responses from the same endpoint. One being the actual data model and one being an error (both as a json structure like this?:
Valid LoginBean response:
{
"id": 1234,
"something": "something"
}
Error response
{
"error": "error message"
}
What you can do is have an entity that wraps both cases and use a custom deserializer.
class LoginBeanResponse {
#Nullable private final LoginBean loginBean;
#Nullable private final ErrorMessage errorMessage;
LoginBeanResponse(#Nullable LoginBean loginBean, #Nullable ErrorMessage errorMessage) {
this.loginBean = loginBean;
this.errorMessage = errorMessage;
}
// Add getters and whatever you need
}
A wrapper for the error:
class ErrorMessage {
String errorMessage;
// And whatever else you need
// ...
}
Then you need a JsonDeserializer:
public class LoginBeanResponseDeserializer implements JsonDeserializer<LoginBeanResponse> {
#Override
public LoginBeanResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
// Based on the structure you check if the data is valid or not
// Example for the above defined structures:
// Get JsonObject
final JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("error") {
ErrorMessage errorMessage = new Gson().fromJson(jsonObject, ErrorMessage.class);
return new LoginBeanResponse(null, errorMessage)
} else {
LoginBean loginBean = new Gson().fromJson(jsonObject, LoginBean.class):
return new LoginBeanResponse(loginBean, null);
}
}
}
Then add this deserializer to the GsonConverterFactory:
GsonBuilder gsonBuilder = new GsonBuilder().registerTypeAdapter(LoginBeanResponse.class, new LoginBeanResponseDeserializer()).create():
apiClient = new Retrofit.Builder()
.baseUrl(url)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gsonBuilder))
.client(httpClient)
.build();
This is the only way I can think of making this work. But as already mentioned this kind of API design is just wrong because status codes are there for a reason. I still hope this helps.
EDIT: What you can then do inside the class where you make the call to that Retrofit (if you already converted from Call<LoginBeanResponse> to Single<LoginBeanResponse> with RxJava) is actually return a proper error. Something like:
Single<LoginBean> getLoginResponse(Map<String, String> queryMap) {
restApi.getLoginResponse(queryMap)
.map(loginBeanResponse -> { if(loginBeanResponse.isError()) {
Single.error(new Throwable(loginBeanResponse.getError().getErrorMessage()))
} else {
Single.just(loginBeanReponse.getLoginBean())
}})
}
You can simply do that by doing this
try
{
String error = response.errorBody().string();
error = error.replace("\"", "");
Toast.makeText(getContext(), error, Toast.LENGTH_LONG).show();
}
catch (IOException e)
{
e.printStackTrace();
}
One possible solution is to make Gson fail on unknown properties. There seems to be an issue raised already(https://github.com/google/gson/issues/188). You can use the workaround provided in the issue page. So the steps are as follows:
Add the workaround ValidatorAdapterFactory to the code base:
public class ValidatorAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// If the type adapter is a reflective type adapter, we want to modify the implementation using reflection. The
// trick is to replace the Map object used to lookup the property name. Instead of returning null if the
// property is not found, we throw a Json exception to terminate the deserialization.
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
// Check if the type adapter is a reflective, cause this solution only work for reflection.
if (delegate instanceof ReflectiveTypeAdapterFactory.Adapter) {
try {
// Get reference to the existing boundFields.
Field f = delegate.getClass().getDeclaredField("boundFields");
f.setAccessible(true);
Map boundFields = (Map) f.get(delegate);
// Then replace it with our implementation throwing exception if the value is null.
boundFields = new LinkedHashMap(boundFields) {
#Override
public Object get(Object key) {
Object value = super.get(key);
if (value == null) {
throw new JsonParseException("invalid property name: " + key);
}
return value;
}
};
// Finally, push our custom map back using reflection.
f.set(delegate, boundFields);
} catch (Exception e) {
// Should never happen if the implementation doesn't change.
throw new IllegalStateException(e);
}
}
return delegate;
}
}
Build a Gson object with this TypeAdaptorFactory:
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ValidatorAdapterFactory()).create()
And then use this gson instance in GsonConverterFactory like below:
apiClient = new Retrofit.Builder()
.baseUrl(url)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson)) //Made change here
.client(httpClient)
.build();
This should throw an error if the unmarshalling step finds an unknown property, in this case the error response structure.
Here is another attempt. General idea: create a custom Converter.Factory based on GsonConverterFactory and a custom Converter<ResponseBody, T> converter based on GsonRequestBodyConverter to parse whole body 2 times: first time as error and second time as actual expected response type. In this way we can parse error in a single place and still preserve friendly external API. This is actually similar to #anstaendig answer but with much less boilerplate: no need for additional wrapper bean class for each response and other similar stuff.
First class ServerError that is a model for your "error JSON" and custom exception ServerErrorException so you can get all the details
public class ServerError
{
// add here actual format of your error JSON
public String errorMsg;
}
public class ServerErrorException extends RuntimeException
{
private final ServerError serverError;
public ServerErrorException(ServerError serverError)
{
super(serverError.errorMsg);
this.serverError = serverError;
}
public ServerError getServerError()
{
return serverError;
}
}
Obviously you should change the ServerError class to match your actual data format.
And here is the main class GsonBodyWithErrorConverterFactory:
public class GsonBodyWithErrorConverterFactory extends Converter.Factory
{
private final Gson gson;
private final GsonConverterFactory delegate;
private final TypeAdapter<ServerError> errorTypeAdapter;
public GsonBodyWithErrorConverterFactory()
{
this.gson = new Gson();
this.delegate = GsonConverterFactory.create(gson);
this.errorTypeAdapter = gson.getAdapter(TypeToken.get(ServerError.class));
}
#Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)
{
return new GsonBodyWithErrorConverter<>(gson.getAdapter(TypeToken.get(type)));
}
#Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit)
{
return delegate.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit);
}
#Override
public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit)
{
return delegate.stringConverter(type, annotations, retrofit);
}
class GsonBodyWithErrorConverter<T> implements Converter<ResponseBody, T>
{
private final TypeAdapter<T> adapter;
GsonBodyWithErrorConverter(TypeAdapter<T> adapter)
{
this.adapter = adapter;
}
#Override
public T convert(ResponseBody value) throws IOException
{
// buffer whole response so we can safely read it twice
String contents = value.string();
try
{
// first parse response as an error
ServerError serverError = null;
try
{
JsonReader jsonErrorReader = gson.newJsonReader(new StringReader(contents));
serverError = errorTypeAdapter.read(jsonErrorReader);
}
catch (Exception e)
{
// ignore and try to read as actually required type
}
// checked that error object was parsed and contains some data
if ((serverError != null) && (serverError.errorMsg != null))
throw new ServerErrorException(serverError);
JsonReader jsonReader = gson.newJsonReader(new StringReader(contents));
return adapter.read(jsonReader);
}
finally
{
value.close();
}
}
}
}
The basic idea is that the factory delegates other calls to the standard GsonConverterFactory but intercepts responseBodyConverter to create a custom GsonBodyWithErrorConverter. The GsonBodyWithErrorConverter is doing the main trick:
First it reads whole response as String. This is required to ensure response body is buffered so we can safely re-read it 2 times. If your response actually might contain some binary you should read and buffer the response as binary and unfortunately retrofit2.Utils.buffer is not a public method but you can create a similar one yourself. I just read the body as a String as it should work in simple cases.
Create a jsonErrorReader from the buffered body and try to read the body as a ServerError. If we can do it, we've got an error so throw our custom ServerErrorException. If we can't read it in that format - just ignore exception as it is probably just normal successful response
Actually try to read the buffered body (second time) as the requested type and return it.
Note that if your actual error format is not JSON you still can do all the same stuff. You just need to change the error parsing logic inside GsonBodyWithErrorConverter.convert to anything custom you need.
So now in your code you can use it as following
.addConverterFactory(new GsonBodyWithErrorConverterFactory()) // use custom factory
//.addConverterFactory(GsonConverterFactory.create()) //old, remove
Note: I haven't actually tried this code so there might be bugs but I hope you get the idea.

Make a GET and POST service call with Retrofit with the use of Protobuf (Protocol Buffer)

Can anyone please give me some example how we can use protobuf in retrofit - I tried but its failed with some error , let me give you a sample of my implementation on that.
I hope you guys will help me.
ApiInterface.java
public interface ApiInterface {
#GET
Call<CommonProto.Country> makeGetRequest(#Url String url);
}
ApiClient.java
public class ApiClient {
public static final String BASE_URL = "**************************";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(Proto3ConverterFactory.create())
.build();
}
return retrofit;
}
}
MainActivity.java
ApiInterface apiService =
ApiClient.getClient().create(ApiInterface.class);
Call<CommonProto.Country> call = apiService.makeGetRequest("Services/CountryServices/GetAllCountry");
call.enqueue(new Callback<CommonProto.Country>() {
#Override
public void onResponse(Call<CommonProto.Country> call, Response<CommonProto.Country> response) {
String bodyString = null;
try {
Log.e("RETROFIT ::::::: ", String.valueOf(response.body())+"TEST");
} catch (Exception e) {
Log.e("RETROFIT ERROR ::::::: ", e.getMessage()+"TEST");
e.printStackTrace();
}
}
#Override
public void onFailure(Call<CommonProto.Country> call, Throwable t) {
// Log error here since request failed
Log.e(TAG, t.toString());
}
}
);
when i run this way i got the error
java.lang.RuntimeException: com.google.protobuf.InvalidProtocolBufferException: Protocol message tag had invalid wire type.
my Proto.java file and also have Proto.proto file both are here in this link,
https://drive.google.com/folderview?id=0B4loQuzINvHCRUlNbk5LUXE1NXM&usp=sharing
Please let me know how to do this GET Req and also I was Struggling with POST Req.
you can create interface like this
public interface LoginInterface {
#FormUrlEncoded
#POST("url goes here")
Call<LoginResponseData> getUserLoginDeatail(#FieldMap Map<String, String> fields);
}
make an instance of retro file and call interface method something like this
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("base url")
.build();
webApis = retrofit.create(WebApis.class);
Call<LoginResponseData> call = webApis.getCurrentRide(keyValue);
call.enqueue(new Callback<LoginResponseData>() {
#Override
public void onResponse(Call<LoginResponseData> call, Response<LoginResponseData> response) {
try {
} catch (Exception e) {
// customizedToast.showToast(context.getResources().getString(
// R.string.exception));
e.printStackTrace();
}
}
#Override
public void onFailure(Call<LoginResponseData> call, Throwable t) {
}
});
for protocol buffer you can find a reference here

Upload file to slack with Retrofit2

I'm trying to upload the file (heap dump) to slack channel using retrofi2 latest release version.
#Override
public void onCreate() {
super.onCreate();
slackApi = new Retrofit.Builder()
.baseUrl("https://slack.com/")
.build() //
.create(SlackApi.class);
}
#Multipart
#GTConverterAnnotation(value = GTConverterAnnotation.GSON)
#POST("api/files.upload")
Call<ResponseBody> uploadFile(
#Part("token") String token,
#Part("file") RequestBody file, #Part("filetype") String filetype,
#Part("filename") String filename, #Part("title") String title,
#Part("initial_comment") String initialComment, #Part("channels") String channels);
RequestBody file = RequestBody
.create(MediaType.parse("multipart/form-data"), heapDump.heapDumpFile);
final Call<ResponseBody> call = slackApi.uploadFile(SlackApi.TOKEN,
file,
null,
heapDump.heapDumpFile.getName(), title, initialComment,
SlackApi.MEMORY_LEAK_CHANNEL);
The following code fail with exception even before execution at "slack.uploaFile" with following exception:
> E/AndroidRuntime: FATAL EXCEPTION: IntentService[com.squareup.leakcanary.AbstractAnalysisResultService]
Process: com.gettaxi.dbx.android, PID: 11127
java.lang.IllegalArgumentException: Could not locate RequestBody converter for class java.lang.String.
Tried:
* retrofit2.BuiltInConverters
at retrofit2.Retrofit.nextRequestBodyConverter(Retrofit.java:298)
at retrofit2.Retrofit.requestBodyConverter(Retrofit.java:258)
at retrofit2.ServiceMethod$Builder.parseParameterAnnotation(ServiceMethod.java:577)
at retrofit2.ServiceMethod$Builder.parseParameter(ServiceMethod.java:328)
at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:201)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:166)
at retrofit2.Retrofit$1.invoke(Retrofit.java:145)
at java.lang.reflect.Proxy.invoke(Proxy.java:397)
at $Proxy13.uploadFile(Unknown Source)
at com.gettaxi.dbx.android.services.LeakSlackUploadService.afterDefaultHandling(LeakSlackUploadService.java:50)
at com.squareup.leakcanary.DisplayLeakService.onHeapAnalyzed(DisplayLeakService.java:86)
at com.squareup.leakcanary.AbstractAnalysisResultService.onHandleIntent(AbstractAnalysisResultService.java:49)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.os.HandlerThread.run(HandlerThread.java:61)
What am I missing? Why it's looking for RequestBody converter for the string?
update
Just created full solution similar to what Matrix suggest:
https://gist.github.com/parahall/cbba57d9d10f6dcd850f
First I want to note that there are other ways of achieving this but i went ahead just making sure the solution works with no major drawbacks.
Please check out my repo here:
Update: the url and repo Name has changed
https://github.com/MaTriXy/Slackrofit
Pasting relevant code as well:
#Multipart
#POST("api/files.upload")
Call<UploadFileResponse> uploadFile(
#Query("token") String token,
#PartMap Map<String, RequestBody> params,
#Query("filetype") String filetype,
#Query("filename") String filename, #Query("title") String title,
#Query("initial_comment") String initialComment, #Query("channels") String channels);
slackApi = new Retrofit.Builder().baseUrl("https://slack.com/").client(new OkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build().create(SlackApi.class);
String str = "Google Places API for Android Samples\n" +
"===================================\n" +
"\n" +
"Samples that use the [Google Places API for Android](https://developers.google.com/places/android/).\n" +
"\n" +
"This repo contains the following samples:";
file = RequestBody.create(MediaType.parse("multipart/form-data"), str.getBytes());
Map<String, RequestBody> map = new HashMap<>();
map.put("file\"; filename=\"heapDump.md\"", file);
call = slackApi.uploadFile(SlackApi.TOKEN, map, "text",
"heapDump.md", "Test Dump", "Check this out", SlackApi.MEMORY_LEAK_CHANNEL);
Later on to activate the call:
call.clone().enqueue(new Callback<SlackApi.UploadFileResponse>() {
#Override
public void onResponse(Call<SlackApi.UploadFileResponse> call, Response<SlackApi.UploadFileResponse> response) {
if (response != null) {
Log.e("GAG", response.body().toString());
}
}
#Override
public void onFailure(Call<SlackApi.UploadFileResponse> call, Throwable t) {
t.printStackTrace();
}
});
I'm using clone to test multiple uploads while this allows me not to rebuild a new call every time i want to use it.
UploadFileResponse is simple:
public static class UploadFileResponse {
boolean ok;
String error;
#Override
public String toString() {
return "UploadFileResponse{" +
"ok=" + ok +
", error='" + error + '\'' +
'}';
}
}
Have you add converter for your Retrofit?
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://api.example.com/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();

Categories