I am using Retrofit 2 and the json that i am getting back if its a successful call looks something like this
{
a:123,
b:456,
c:789
}
But when there is an error being returned from the server the error json looks like
{
error:"Error Message"
}
The error here is not an error in the connection,the call completed successfully.How do i configure retrofit to process this so i know if i received the first json or the second and display a message accordingly.
You could use a BaseResponse class and the rest of your responses class inherits from this class.
For example:
public class BaseResponse implements Serializable {
#SerializedName("error")
public String error;
}
You probably need model something like:
public class Example {
public int a;
public int b;
public int c;
#Nullable public String error;
public boolean isError() {
return error != null && !error.isEmpty();
}
}
and then in retrofit callback just check
Example example = response.body();
if (example.isError) {
// show error message
} else {
// show successfull response
}
Related
I've been using Gson.toJson to convert my object for a request:
class Convertable {
private String[] mApples;
private String[] mOranges;
...
}
Which gave me JSON that looks something like this:
{
"apples": [
"ahadhahajjajajaj",
"afwqrbvlnwegoihw",
"bnobnwoibbwwrbwb"
],
"oranges": [
"ahadhaha",
"abvlnwew",
"bnobrbwb"
],
...
}
Now my object has changed to
class Convertable {
private String[] mApples;
private MyChild[] mOranges;
...
}
class MyChild {
private String mId; //is the string the json contained before
... //contains other fields the request doesnt need
}
But if it's possible I want to do something just as simple. How do I achieve this? Can it be done without writing a custom serializer and having to manually convert everything else in the object?
Edit - More info:
My current approach is as follows, I need to add and expose a method:
class Convertable {
private String[] mApples;
private transient MyChild[] mOranges;
...
#SerializedName("oranges")
public String[] getMyChildIds() {
...
}
}
I'd like to know if there is a better way than this
You don't need a custom converter for the Convertable class, just for the MyChild. And register it using GsonBuilder#registerTypeHierarchyAdapter. That way you don't need to change anything about Convertable.
public class MyChildAdapter extends TypeAdapter<MyChild> {
#Override
public void write(JsonWriter out, MyChild myChild)
throws IOException
{
out.value(myChild.getId());
}
#Override
public MyChild read(JsonReader in) throws IOException {
// implement it if needed
return null;
}
}
I have the following Object and i want to use RxJava in order to create a new object. The logic behind this is that each article has a lot of comments. And it finds the correct comments using the ArticleData.commentId and the Comment.id.
public class ArticlesResponse {
public List<ArticleData> articles;
public List<Data> comments;
}
public class Data {
public int id;
public String link;
public String title;
public String author
public String body;
}
public class ArticleData extends Data {
public List<int> commentId;
}
So how can i use Rxjava in order to create the following object
public class Article extends Data {
public List<Comments> comments;
}
public class Comments extends Data {
// comments will have more attributes in the feature
// so use a seperate object
}
I know that i have to use the flapMap and the filter in order to parse the "ArticleResponse" but i don't know how to put all this together.
Furthermore the "ArticleResponse" is being generated from a json which i got from Retrofit, so i guess it will be better to use RxJava since i already have the Observable instead of putting nested for's inside my Callback.
I assume you means that articlesResponse.comments is a list contains all Comments of these all ArticleData, although I don't think wrap these data together and do the map operation in client is a good idea, this job should be done at server.
And I think maybe your ArticlesResponse's comments field should be a List<Comments>.
With these assumption, the code below may do the job you want (I put them in a TempTest class, and define an interface you described, and mock it to pass javac compile, and I also use Java 8 lambda grammar for code simplicity).
public class TempTest {
public static class Data {
public int id;
public String link;
public String title;
public String author;
public String body;
}
public static class ArticleData extends Data {
public List<Integer> commentId;
}
public static class Comments extends Data {
// comments will have more attributes in the feature
// so use a seperate object
}
public static class ArticlesResponse {
public List<ArticleData> articles;
public List<Comments> comments;
}
public class Article extends Data {
public List<Comments> comments;
}
public interface TestInterface {
Observable<ArticlesResponse> getArticle();
}
public static Comments findCommentWithId(int commentId, List<Comments> comments) {
for (Comments comment : comments) {
if (comment.id == commentId) {
return comment;
}
}
return null;
}
#Test
public void simpleTestcase() {
// assume you means that articlesResponse.comments is a list contains all Comments of these
// all ArticleData, although I don't think wrap these data together and do the map operation
// in client is a good idea, this job should be done at server
TestInterface testInterface = mock(TestInterface.class);
testInterface.getArticle().map(articlesResponse -> {
List<Article> result = new ArrayList<>();
// for each ArticleData in articlesResponse.articles
for (ArticleData articleData : articlesResponse.articles) {
// get all Comments from articlesResponse.comments
Article article = new Article();
// ... copy Data field from articleData to article
article.comments = new ArrayList<>();
for (Integer id : articleData.commentId) {
Comments comment = findCommentWithId(id, articlesResponse.comments);
if (comment != null) {
article.comments.add(comment);
}
}
result.add(article);
}
return result;
}).subscribe(articles -> {
for (Article article : articles) {
System.out.println(article);
}
});
}
}
Kind of confused at what your actual question is, so hopefully this helps. Retrofit can return an Observable for you, which should make RxJava integration easy. For example, in your service you could make:
#GET(<your endpoint>)
Observable<ArticlesResponse> getArticles();
And call it like:
<yourService>.getArticles()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedules.mainThread())
.subscribe() // manipulate how you want
I would like Retrofit to raise custom exceptions depending on the server response. For example in the following structure:
{
"code":0,
"message":"OK",
"data":{....}
}
I would like to raise an exception for subscribers if code is anything other than 0. How is it possible using Retrofit and Rx? I would much prefer to write this logic only once and have it applied to all observables returned by retrofit.
I would like to raise an exception for subscribers if code is anything other than 0. How is it possible using Retrofit and Rx?
You can use a Observable.flatMap operator:
api.request().flatMap(response -> {
if (response.getCode() != 0) {
return Observable.error(new Exception("Remote error occurred"));
}
return Observable.just(response);
});
I would much prefer to write this logic only once and have it applied to all observables returned by retrofit.
Unfortunately, there is not way to do it using retrofit and rx-java. You have to write the code above for every retrofit call. The only thing you can do is to use Observable.compose method and reduce the amount of boilerplate you actually have to write.
api.request().compose(new ResponseTransformer<Response>());
And here is the ResponseTransformer class:
public static class ResponseTransformer<T extends Response> implements Observable.Transformer<T, T> {
#Override
public Observable<T> call(final Observable<T> observable) {
return observable.flatMap(response -> {
if (response.getCode() != 0) {
return Observable.error(new Exception("Remote error occurred"));
}
return Observable.just(response);
});
}
}
UPDATE
Well, as I said, there is no way to avoid boilerplate code using only retrofit and rxjava, but you can workaround it with dynamic proxies (note that you don't need to call compose anymore):
final Api api = restAdapter.create(Api.class);
final ClassLoader loader = api.getClass().getClassLoader();
final Class<?>[] interfaces = api.getClass().getInterfaces();
final Api proxy = (Api) Proxy.newProxyInstance(loader, interfaces, new ResponseInvocationHandler(api));
proxy.request().subscribe(response -> {
System.out.println("Success!");
});
ResponseInvocationHandler class:
public static class ResponseInvocationHandler implements InvocationHandler {
private final Object target;
public ResponseInvocationHandler(final Object target) {
this.target = target;
}
#Override
#SuppressWarnings({"unchecked"})
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
final Object result = method.invoke(target, args);
if (result instanceof Observable) {
return Observable.class.cast(result).compose(new ResponseTransformer<>());
}
return result;
}
}
I would suggest a different approach.
You will need to implement a custom OkHttp client with custom Interceptor.
OkHttpClient client = new OkHttpClient();
client.interceptors().add(new MyInterceptor());
mAdapter = new RestAdapter.Builder().setEndpoint(Consts.ENDPOINT).setClient(new OkClient(client))
.setLogLevel(RestAdapter.LogLevel.BASIC).build();
In your interceptor depending on the code returned you can proceed normally or throw an exception.
Something like this:
public class MyInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if(response.code() == 0) {
throw new RuntimeException("Something went wrong!");
}
return response;
}
}
1 custom Observable.Operator :
public class YourOperator implements Observable.Operator{
public void onNext(Data data){
if (data.code != 0 ){
//raise your custom Exception
}
}
public void onError(Throwable e){
//handler Exception
}
}
2 user like:
api.youRequest()
.lift(new YourOperator())
.subscribe(....);
I started using Retrofit recently. I don't know much about it. I have googled this issue and no answers suite my problem.
This is JSON response
{
"results": [
{
"description_eng": "This is second time testing",
"img_url": "-",
"title_eng": "Second test"
},
{
"description_eng": "Hello 1 2 3, I am testing.",
"img_url": "https://fbcdn-sphotos-f-a.akamaihd.net/hphotos-ak-xpa1/t31.0-8/s720x720/10838273_816509855058935_6428556113200361121_o.jpg",
"title_eng": "Test"
}
]
}
This is Feed Class
public class Feed {
public List<Results> results;
class Results{
String description_eng,img_url,title_eng;
}
}
This is the interface
public interface GetApi {
#GET("/api.json")
public void getData(Callback<List<Feed>> response);
}
I got json_illegal_syntax Exception.
This is how I solved this problem, by creating empty constructors.
Feed.class
public class Feed{
private List<Result> results;
public Feed(){}
public List<Result> getFeed(){
return this.results;
}
public void setFeed(List<Result> results) {
this.results = results;
}
}
Result.class
public class Result{
private String description_eng;
private String img_url;
private String title_eng;
public Result(){}
//getters and setters
}
GetApi.class
public interface GetApi {
#GET("/api.json")
public void getData(Callback<Feed> response);
}
Retrofit uses Gson by default to convert HTTP bodies to and from JSON. If you want to specify behavior that is different from Gson's defaults (e.g. naming policies, date formats, custom types), provide a new Gson instance with your desired behavior when building a RestAdapter.
Gson can not automatically deserialize the pure inner classes since their no-args constructor also need a reference to the containing Object which is not available at the time of deserialization. You can address this problem by either making the inner class static or by providing a custom InstanceCreator for it. Here is an example:
public class A {
public String a;
class B {
public String b;
public B() {
// No args constructor for B
}
}
}
NOTE: The above class B can not (by default) be serialized with Gson.
You should read more about GSON library
#Ye Min Htut Actually, even better is to write Feed class with generics.
public class FeedList<T>{
private List<T> feeds;
public Feed() {
}
public List<T> getFeed(){
return this.feeds;
}
public void setFeed(List<T> results) {
this.feeds = feeds;
}
}
and if there is something similar with only one object, than you can remove List part and get/set single object.
Now you can call FeedList<Result> or with whatever object you want.
GetApi.class
public interface GetApi {
#GET("/api.json")
public void getData(Callback<FeedList<Result>> response);
}
#Ye Min Htut I will suggest you to make Model/POJO classes using ROBOPOJO Generator It will generate All model classes for you, you don't need to make it by your self, Will also help you in future while creating model class. It just need JSON string and click will make your job done
I'm working on an android application using robospice, spring and jackson. Everything works fine except that cache results don't have relations of POJO classes.
For instance;
public class User extends SampleBase {
public int id;
public String name;
public Address address;
}
public class Address extends SampleBase {
public int id;
public String street;
public String city;
}
public class SampleBase {
// Base class of all POJO classes
}
When I send a request to get a user, I get all the values properly. However, when I try to get a user from cache, the address field returns null. If there's a relation between two classes, those relation fields are null but other fields are OK.
The result of request:
User:
id: 1
name: "Test User"
address: Address Object
The result of cache:
User:
id: 1
name: "Test User"
address: null
In my design, all of the POJO classes extend SampleBase. And there is only one RequestListener:
public class SampleRequestListener implements RequestListener<SampleBase> {
#Override
public void onRequestSuccess(SampleBase result) {
// Some operations
}
#Override
public void onRequestFailure(SpiceException e) {
// Some operations
}
}
I don't know if it's about my request listener but this works fine while sending requests. Is there something else that I should do to get the related objects of a cached object?
It is strange, we often do this and it works fine. Try to find some solution on jackson forum, RS is just a wrapper here.