We are currently working on setting up Retrofit for an XML API - unfortunately each request can return a response with one of two different root elements.
Normally, each response looks like this (of course, the actual elements contained within the <response>
tag vary with each request):
<?xml version="1.0" encoding="UTF-8"?> <response> <SomeInfo>Some Info</SomeInfo> <MoreInfo>More Info</MoreInfo> </response>
And each error looks like this (the structure here is the same for each response):
<?xml version="1.0" encoding="UTF-8"?> <error> <code>125002</code> <message></message> </error>
Now, the only way we've found so far to get this work in a somewhat generic way is the following:
public interface Api { @GET("/api/sessionToken") Observable<ResponseBody> requestSessionToken(); @GET("/api/pinStatus") Observable<ResponseBody> requestPinStatus(); } public class RestClient { public RestClient() { // ... mApiService = retrofit.create(Api.class); } public Observable<PinStatusResponse> requestPinStatus() { return mApiService.requestPinStatus() .flatMap(foo(PinStatusResponse.class, PinStatusResponseData.class)); } public Observable<SessionTokenResponse> requestSessionToken() { return mApiService.requestSessionToken() .flatMap(foo(SessionTokenResponse.class, SessionTokenResponseData.class)); } private final <O extends MyResponse, I> Func1<ResponseBody, Observable<T>> foo(final Class<O> outerCls, final Class<I> innerCls) { return new Func1<ResponseBody, Observable<O>>() { @Override public Observable<O> call(ResponseBody responseBody) { try { final String xmlString = responseBody.string(); final XmlPullParser parser = Xml.newPullParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); parser.setInput(new ByteArrayInputStream(xmlString.getBytes(Charset.forName("UTF-8"))), null); parser.nextTag(); final String rootTag = parser.getName(); final Serializer serializer = new Persister(); if (TextUtils.equals(rootTag, "error")) { final MyError myError = serializer.read(MyError.class, xmlString); return Observable.just((O) outerCls.getConstructor(MyError.class, innerCls).newInstance(myError, null)); } else if (TextUtils.equals(rootTag, "response")) { final I data = serializer.read(innerCls, xmlString); return Observable.just((T) outerCls.getConstructor(MyError.class, innerCls).newInstance(null, data)); } } catch (XmlPullParserException e) { return Observable.error(e); } catch (IOException e) { return Observable.error(e); } catch (Exception e) { return Observable.error(e); } return Observable.error(new Exception("Should not be reached...")); } }; } }
Where the Response classes look like this:
public abstract class MyResponse<T> { public final MyError error; public final T data; protected MyResponse(MyError error, T data) { this.error = error; this.data = data; } }
and:
public final class PinStatusResponse extends MyResponse<PinStatusResponseData> { public PinStatusResponse(MyError error, PinStatusResponseData data) { super(error, data); } }
And all the *Data
classes correspond directly to the (non-error) XML responses.
Now, my question is: Is there really no easier way to solve this? (And if so, is this a sign of bad API design?).
No comments:
Post a Comment