Preface: I'm using Retrofit to handle my API calls and Realm(realm.io) to store the data.
The API im dealing with uses the following structure:
Array Response
{
"response":
[
{
"objectField1":"abc"
"objectField2":"abc"
"objectField3":"abc"
"objectField4":"abc"
},
{
"objectField1":"abc"
"objectField2":"abc"
"objectField3":"abc"
"objectField4":"abc"
}
]
}
Single object response
{
"response":
{
"objectField1":"abc"
"objectField2":"abc"
"objectField3":"abc"
"objectField4":"abc"
}
}
All api responses are contained in a response object either in an array (if result size > 1) or an object (if result size == 1).
I currently have my API call as follows:
#GET("/api/myEndpoint")
void getAllExampleObjects(Callback<MyRealmClass> callback);
How can I serialise the API response (handling both array and single object cases) to place them in my realm?
Christian from Realm here.
If you have a single REST API call that can return both a list and a single object, you will have to do something manually. As colriot points out you will have to write your own GSON deserializer. For ideas how to write one see a very good answer in this SO post: How to handle parameters that can be an ARRAY or OBJECT in Retrofit on Android?
To get the objects into Realm you can use realm.copyToRealm(objects) in the following way:
#GET("/api/myEndpoint")
void getAllExampleObjects(Callback<List<MyRealmClass>> callback);
Callback callback = new Callback() {
#Override
public void success(List<MyRealmClass> objects, Response response) {
realm.beginTransaction();
realm.copyToRealm(objects);
realm.commitTransaction();
}
#Override
public void failure(RetrofitError retrofitError) {
}
};
Related
Server sends response in case of success (code 2xx):
{
"user":"User",
"age":16
}
And in case of fail (4xx) the following Json is sent inside errorBody:
{
"errorMessage":"Server is not working",
"action":{
...
}
}
I am using Retrofit v2.4.0 and RxJava2.
How can I create custom CallAdapter for my case? I have read Square's ErrorHandlingAdapter but it was without RxJava2
it is highly recommended using same object because of enque of retrofit, you can do this using generic model
public class GenericResponseModel {
public int statusCode;
public String message;
public Object data;
}
and then parse into a particular model in your onNetworkSuccess and onNetworkError something like this:
HomeModel homeModel = new Gson().fromJson(new Gson().toJson(genericResponseModel.data), HomeModel[].class);
using this approach you can make BaseModel and can treat with different responses too. I hope this is the answer that you are finding.
you can remove statusCode, message if no in response
This question already has answers here:
Unable to create call adapter for class example.Simple
(19 answers)
Closed 7 years ago.
I am trying to get response from the following Api :
https://api.github.com/users/username
But I don't know how to get response as String so that I can use the String to parse and get the JSONObject.
Retrofit version used:
retrofit:2.0.0-beta1
I have tried this until now:
public interface GitHubService {
#GET("/users/{user}")
public String listRepos(#Path("user") String user,Callback<String> callback);
}
retrieving :
GitHubService service = retrofit.create(GitHubService.class);
service.listRepos("username", new Callback<String>() {
#Override
public void onResponse(Response response) {
System.out.println(response.toString());
}
#Override
public void onFailure(Throwable t) {
}
});
exception:
Caused by: java.lang.IllegalArgumentException: Could not locate call adapter for class java.lang.String. Tried:
* retrofit.ExecutorCallAdapterFactory
at retrofit.Utils.resolveCallAdapter(Utils.java:67)
at retrofit.MethodHandler.createCallAdapter(MethodHandler.java:49)
Any help would be really appreciated.
** Update ** A scalars converter has been added to retrofit that allows for a String response with less ceremony than my original answer below.
Example interface --
public interface GitHubService {
#GET("/users/{user}")
Call<String> listRepos(#Path("user") String user);
}
Add the ScalarsConverterFactory to your retrofit builder. Note: If using ScalarsConverterFactory and another factory, add the scalars factory first.
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
// add other factories here, if needed.
.build();
You will also need to include the scalars converter in your gradle file --
implementation 'com.squareup.retrofit2:converter-scalars:2.1.0'
--- Original Answer (still works, just more code) ---
I agree with #CommonsWare that it seems a bit odd that you want to intercept the request to process the JSON yourself. Most of the time the POJO has all the data you need, so no need to mess around in JSONObject land. I suspect your specific problem might be better solved using a custom gson TypeAdapter or a retrofit Converter if you need to manipulate the JSON. However, retrofit provides more the just JSON parsing via Gson. It also manages a lot of the other tedious tasks involved in REST requests. Just because you don't want to use one of the features, doesn't mean you have to throw the whole thing out. There are times you just want to get the raw stream, so here is how to do it -
First, if you are using Retrofit 2, you should start using the Call API. Instead of sending an object to convert as the type parameter, use ResponseBody from okhttp --
public interface GitHubService {
#GET("/users/{user}")
Call<ResponseBody> listRepos(#Path("user") String user);
}
then you can create and execute your call --
GitHubService service = retrofit.create(GitHubService.class);
Call<ResponseBody> result = service.listRepos(username);
result.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Response<ResponseBody> response) {
try {
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Throwable t) {
e.printStackTrace();
}
});
Note The code above calls string() on the response object, which reads the entire response into a String. If you are passing the body off to something that can ingest streams, you can call charStream() instead. See the ResponseBody docs.
I'm using Retrofit/OkHttp in a project and recently I've discovered RxJava. Combining it with Retrofit seems easy and straightforward but with regular async callbacks in Retrofit in success(...) we are receiving the parsed POJO and the Response. This is quite useful and in some of my callbacks I'm using both of these objects. I can't seem to find a way to do this with RxJava.
Is it possible to obtain the parsed POJO and the Response object at the same time?
The way RxJava works is that the onNext method always emits exactly one value, so you won't be able to get something like (as it would break the contract):
onNext(T value, Response respone);
The closest could be an Observable<ResponseAndPojo<T>> where ResponseAndPojo is as follows:
public class ResponseAndPojo<T> {
private final T value;
private final Response response;
public ResponseAndPojo(T value, Response response) {
this.value = value;
this.response = response;
}
// add getters here
}
Such an Observable would then emit items with:
onNext(ResponseAndPojo<T> responseAndPojo)
and you would have access to both the Response and the POJO.
Now, how to construct such an Observable:
One way would be to create some kind of Subject (maybe a BehaviorSubject, but for single requests it does not really matter) and then in the Retrofit success method put the return values into the Subject.
So, in some kind of RetrofitWrapper class of your own you would have
public Observable<ResponseAndPojo<YourPojoClass>> getResponseAndPojoObservable() {
final BehaviorSubject<ResponseAndPojo<YourPojoClass>> retrofitSubject = BehaviorSubject.<ResponesAndPojo<YourPojoClass>>create();
yourRetrofitService.getSomething(new Callback<YourPojoClass>() {
#Override
public void success(YourPojoClass pojo, Response response) {
retrofitSubject.onNext(new ResponseAndPojo(pojo, response);
retrofitSubject.onCompleted();
}
});
return retrofitSubject;
}
As you can see, from the outside the Subject looks like an Observable<ResponseAndPojo<YourPojoClass>>, which is exactly what we wanted.
The REST Api I'm working with has custom codes and messages which are sent from server depending on the state, I would like to implement a custom Callback<T> that calls the success method only if the status code was 0.
Example SUCCESS Response received from server:
{
"code":"0",
"message":"success",
"data": {
"actual_data":"goes_here",
"need_to_construct_objects","from_data"
}
}
Example of FAILURE Response:
{
"code":"301",
"message":"wrong_password",
"data": {
"actual_data":"will_be_null",
"no_need_to_construct_objects","from_data"
}
}
code and message are returned by all requests, the data contains the actual response values, so I would like to do the following:
Check the code and message and only call success() if code is 0.
Call failure() if request failed or code != 0
Construct custom objects based on the data response and pass them via success()
What is the best way to do this? I searched everywhere and could not find a good solution. The only one I got was to let all custom objects have the code and message fields too and check their values inside success(), but this could cause problems in future in case someone forgets to check the code before proceeding.
You can do that quickly by just making an abstract class that implements Callback, and declare your own abstract success and failure methods. The abstract class will handle Retrofit's standard callback methods, interpret the response and call the abstract methods accordingly.
I think another possible approach to this is to override Retrofit's Client interface to build your own Response object.
If you extend OkClient, it can go like this:
public class CustomClient extends OkClient {
#Override public Response execute(Request request) throws IOException {
Response originalRespone = super.execute(request);
int statusCode = 0;
//TODO: read JSON response here (using GSON or similar, and extract status code and message... etc.)
//Convert the status code to HTTP standard status codes, according to the documentation you have.
if(statusCode == 0) statusCode = 200;
//Reconstruct a Response object
return new Response(originalResponse.getUrl(), statusCode, originalResponse.getReason() /*should probably replace with parsed message*/, originalResponse.getHeaders(), originalResponse.getBody());
}
This may be more work than handling your case in Callback, but I think it can help if at some point the API transitions to RESTful API conventions.
This solution comes with its own problem though, because that means the JSON conversion will run twice. One in your client, and another one by Retrofit. Not sure the correct way to do that at the moment. Probably something around TypedInput and a dummy Converter that passes already converted objects.
Create a custom ResponseBodyConverter like this:
public class CustomResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final TypeAdapter<T> adapter;
CustomResponseBodyConverter(TypeAdapter<T> adapter) {
this.adapter = adapter;
}
#Override
public T convert(ResponseBody value) throws IOException,CustomException {
String json = "";
try {
String body = value.string();
json = new JSONObject(body).getJSONObject("data").toString();
int code = new JSONObject(body).getInt("code");
String message = new JSONObject(body).getString("message");
if(code != 0){
throw new CustomException(message);
}
} catch (JSONException e) {
e.printStackTrace();
}
return adapter.fromJson(json);
}
}
It's a better idea to implement a custom callback. You can an example about it below.
public abstract class DefaultRequestCallback<T> implements Callback<T> {
public abstract void failure(Meta meta);
public abstract void success(T responseBean);
#Override
public void success(T baseResponseBean, Response response) {
// You can check your responsebean's error code and
// convert it to a default error
BaseResponseBean bean = (BaseResponseBean) baseResponseBean;
if (bean == null) {
failure(new Meta(ApplicationConstants.ERROR_RETROFIT, "Unknown Error!"));
} else if (bean.getMeta() != null && bean.getMeta().getCode() != ApplicationConstants.RESULT_SUCCESS) {
failure(bean.getMeta());
} else {
success(baseResponseBean);
}
}
#Override
public void failure(RetrofitError error) {
// Convert default error to your custom error.
Meta meta = new Meta(ApplicationConstants.ERROR_RETROFIT, "Error Unknwon");
failure(meta);
}
}
Give your custom callback to your retrofit service interface method.
void yourMethod(DefaultRequestCallback<YourResponseBean> callback);
Good Luck.
This will at least get you started. You can basically create your own custom callback and then handle the success. Look at what was sent and do what you need to.
public class CustomCallback implements Callback {
#Override
public void success(Object o, Response response) {
//Check for success
//if( Success )
//callback.success(o, response);
//else
//Check for error
//callback.failure(error);
}
}
In your case, you can have a class that maps your json response:
class CustomResponse {
String code;
String message;
Data data;
static class Data {
String actualData;
String needToContructObjects;
String noNeedToContructObjects;
}
}
Then, since you're back to the java objects world, you can have a factory-like object inside your success method callback that creates the desired object based on the returned custom response. If you want to get this response in the failure callback, I'd reconsider using Retrofit, since your API is not following a good Rest design.
Although this is plenty possible, and understanding you might not be involved on the API development, be aware this is not a good API design approach. If you are POSTing a login request to the server, you can understand this request as a request to create a resource (an authenticated user session, for instance). If you don't send the correct parameters (the correct username and password in this specific case), the server should reject the resource creation request and send back a 4-hundred-something (4xx) http status code indicating your request was not correct somehow. Retrofit would understand this 4xx status code and call your failure callback, where you could handle the response appropriately.
I have an auth REST api that returns the following JSON response (I am following http://jsonapi.org convention)
{ "user" : { "id" : 1, "first_name" : "Jack" } }
I noticed that Retrofit/Gson does not parse out the root user object automagically? so instead of giving a User object in my success callback, I am doing the following, was wondering if theres a better way to do this? I am new to Retrofit/Android.
authService.signIn(authJson, new Callback<JsonObject>() {
#Override
public void success(JsonObject jsonObject, Response response) {
User user = new Gson().fromJson(jsonObject.get("user"), User.class);
progress.hide();
signInSuccess(user);
}
#Override
public void failure(RetrofitError retrofitError) {
}
});
I think you should just create a wrapping class like that:
class UserWrapper {
User user;
}
and make that the return type of the Retrofit interface.
There's an issue with your solution as well. If I remember correctly, Retrofit streams the network data directly to Gson on the fly. Your solution, on the other hand, will parse User object after the full response is received, and on the main thread as well.
The penalty may not be that significant for a single request, but it's something you'd probably want to avoid for general optimization.
You can write your own deserializer, register it in GsonBuilder and pass it to the RestAdapter
Registering Type:
GsonBuilder gb = new GsonBuilder();
gb.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
gb.registerTypeAdapter(User.class, new UserDeserializerDeserializer());
OkClient okClient = new OkClient();
Builder restAdapter = new RestAdapter.Builder();
restAdapter.setEndpoint(BASE_URL);
restAdapter.setExecutors(Executors.newCachedThreadPool(), new MainThreadExecutor());
restAdapter.setConverter(new GsonConverter(gb.create()));
mRestAdapter = restAdapter.build();//
UserDeserializer class:
public class UserDeserializer implements JsonDeserializer<User>{
private static Gson sGson = new Gson();
#Override
public User deserialize(JsonElement arg0, Type arg1,
JsonDeserializationContext arg2) throws JsonParseException {
JsonObject userObject = arg0.getAsJsonObject().getAsJsonObject("user");
return sGson.fromJson(userObject, arg1);
}
You are deserializing the response in the success method, which could hang the UI (under assumption you are using MainThreadExecutor for callbacks) in the cases where you have huge huge response.
Approach with writing your own deserializer will parse the response on the background thread too.