Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GDG August 2015 - Retrofit + GSON

GDG August 2015 - Retrofit + GSON

Slides from GDG Dublin, presenter the use of Retrofit & GSON in Zendesk's client apps

Avatar for Alan Cooke

Alan Cooke

August 01, 2015
Tweet

More Decks by Alan Cooke

Other Decks in Technology

Transcript

  1. Introduction aka The Problem • Legacy code base built using

    old style pure OOP • Inheritance overkill • Manual JSON parsing • Huge learning curve • Hard to maintain & Poor serviceability • Mixture of concerns
  2. public void minimalInitModels() throws RequestException { final Thread thread =

    Thread.currentThread(); final List<RequestError> errors = new ArrayList<RequestError>(); fetchModels(new RequestCallback<BaseModel>() { public void requestCompleted(ZDModelObject<BaseModel> result) { thread.interrupt(); } public void requestErrored(RequestError error) { errors.add(error); } }, true); try { Thread.sleep(60000); } catch (InterruptedException e) {} if (errors.size() > 0) { logger.error("Error logging in.", errors.get(0)); throw new RequestException(0,errors.get(0).getErrorMessage()); } }
  3. Guiding Principles for new Client • Limited or no carry

    over from our old API client project • Let models be models ◦ Immutable as much as possible • Hide Retrofit from clients • Be consistent!
  4. What is Retrofit • Open Source project from Square •

    Pure Java but has support for Android • Built on the premise of annotated Interfaces • Delegate construction of concrete API client to Retrofit • Built on top of OKIO and OKHttp (optionally) Link: http://square.github.io/retrofit/
  5. What is GSON • JSON Serialization Library from Google •

    Powerful reflection based library • API convention is at the core of the library • Not every library is perfect but GSON is flexible enough to correct your ways Link: https://github.com/google/gson (New home on Github)
  6. SafeCallbacks public class SafeZendeskCallback<T> extends ZendeskCallback<T> { private boolean cancelled;

    private final ZendeskCallback<T> callback; private static final String LOG_TAG = "SafeZendeskCallback"; /** * Constructor to wrap a {@link ZendeskCallback} so that it can be cancelled * * @param callback The callback to wrap */ public SafeZendeskCallback(ZendeskCallback<T> callback) { this.callback = callback; this.cancelled = false; }
  7. @Override public void onSuccess(T result) { if (!cancelled && callback

    != null) { callback.onSuccess(result); } else { Logger.w(LOG_TAG, "Operation was a success but callback is null or was cancelled"); } } @Override public void onError(ErrorResponse error) { if (!cancelled && callback != null) { callback.onError(error); } else { Logger.e(LOG_TAG, error); } } /** * This cancels the callback. This means that the wrapped callback will not be called if this method was called. */ public void cancel() { this.cancelled = true; }
  8. /** * Convenience method to wrap a {@link ZendeskCallback} in

    a {@linkplain SafeZendeskCallback} * * @param callback The callback to wrap * @param <T> The type of object that the onSuccess method will return * @return The SafeZendeskCallback which wraps the supplied ZendeskCallback */ public static <T> SafeZendeskCallback<T> from(ZendeskCallback<T> callback) { return new SafeZendeskCallback<>(callback); }
  9. Request Extractor /** * Interface used to extract data from

    type {@code E} received over the network * and convert it to type {@code F} * * @param <E> type of data received through the network * @param <F> type of data that is needed by a {@link ZendeskCallback} */ public interface RequestExtractor<E, F> { F extract(E data); }
  10. Request Extractor public RetrofitZendeskCallbackAdapter(ZendeskCallback<F> callback, RequestExtractor<E, F> extractor) { this.mCallback

    = callback; this.mExtractor = extractor; } /** * More convenient version of {@link #RetrofitZendeskCallbackAdapter(ZendeskCallback, RequestExtractor)}. * Can be used if {@code E} and {@code F} are of the same type. A {@link #DEFAULT_EXTRACTOR} will be used. * * @param callback a {@link ZendeskCallback} that should be notified if * {@link #success(Object, Response)} or * {@link #failure(RetrofitError)} was called */ public RetrofitZendeskCallbackAdapter(ZendeskCallback<F> callback) { //noinspection unchecked this(callback, DEFAULT_EXTRACTOR); }
  11. Request Extractor @Override public void success(final E e, final Response

    response) { if (mCallback != null) { mCallback.onSuccess(mExtractor.extract(e)); } } @Override public void failure(final RetrofitError error) { if (mCallback != null) { mCallback.onError(new RetrofitErrorResponse(error)); } }
  12. Recipes of working with GSON • Use a single GSON

    builder • TypeAdapters FTW • Annotations are great but… keep em to a minimum
  13. Use a single GSON Builder public static Gson build() {

    com.google.gson.GsonBuilder builder = new com.google.gson.GsonBuilder(); builder.registerTypeAdapterFactory(new LowercaseEnumTypeAdapterFactory()); builder.excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT); builder.setFieldNamingStrategy(new LocaleAgnosticFieldNamingStrategy()); Type auditListType = new TypeToken<List<AuditEvent>>(){}.getType(); builder.registerTypeAdapter(auditListType, new AuditEventDeserializer()); builder.registerTypeAdapter(TicketChangeAuditEvent.class, new TicketChangeAuditEventDeserializer()); builder.registerTypeAdapter(TicketCreationAuditEvent.class, new TicketCreateAuditEventDeserializer()); builder.registerTypeAdapter(SearchResult.class, new SearchResultDeserializer()); builder.registerTypeAdapter(Date.class, new DateTypeAdapter()); builder.registerTypeAdapter(NullableLong.class, new NullableLongTypeAdapter()); builder.registerTypeAdapter(MacroAction.class, new MacroActionDeserialiser()); builder.registerTypeAdapter(Via.class, new ViaSerializer()); return builder.create(); }
  14. TypeAdapter FTW /** * This is a workaround for this

    issue: https://github.com/google/gson/pull/652 */ private static class LocaleAgnosticFieldNamingStrategy implements FieldNamingStrategy { @Override public String translateName(Field field) { return separateCamelCase(field.getName(), "_").toLowerCase(Locale.ENGLISH); } /** * Converts the field name that uses camel-case define word separation into * separate words that are separated by the provided {@code separatorString}. */ private String separateCamelCase(String name, String separator) { StringBuilder translation = new StringBuilder(); for (int i = 0; i < name.length(); i++) { char character = name.charAt(i); if (Character.isUpperCase(character) && translation.length() != 0) { translation.append(separator); } translation.append(character); } return translation.toString(); } }
  15. Annotations in Action public class Brand { private Long id;

    private String url; private String name; private Boolean active; @SerializedName("default") private Boolean isDefault; private Logo logo; private Date createdAt; private Date updatedAt; }
  16. Results of the Refactor • 10x performance improvement ◦ Built

    on the shoulder of giants • Developer Performance increased 300x ◦ Productivity has increased significantly ◦ New developers productive in a day • Serviceability ◦ Easier to debug customer issues ◦ Retrofit logging is awesome
  17. Where to next • RxJava Support, expose Retrofit’s support for

    it. • Document all the things • Open Source