Save 37% off PRO during our Black Friday Sale! »

Engineering Wunderlist for Android

Engineering Wunderlist for Android

Do you want to know how Wunderlist for Android has been built? Now you can :-)
Here I explain the architecture in layers we have made for our app and more cool stuff about how we have built this multimillion productivity app.

9fa66713006f997a5c658adabd2add25?s=128

Cesar Valiente

September 29, 2014
Tweet

Transcript

  1. Engineering Wunderlist for Android. Cesar Valiente

  2. Wunderlist, 6Wunderkinder and me

  3. First of all… 3 • Wunderlist 2 was created as

    a monolithic app. • Wunderlist 3 (from now on, just Wunderlist) has been built to be highly independent between layers. • Real time sync.
  4. 1. General architecture

  5. 1.2 Three layers 5 Android Layer Sync Layer SDK Layer

    Presentation layer (UI/Android stuff) Model layer (Business logic) Data layer (Accessing to the API data) Android project Java project Java project Sync boundaries SDK boundaries
  6. 1.3 What does it mean? 6 • Independent layers. Accessibility.

    • Clean architecture. • Abstract, easy to change, modular —> interfaces.
  7. package com.wunderlist.sdk;

  8. 2. Sdk • Websocket (real time). • REST. • Services

    • API data models. • Serializers/deserializers. • Interfaces/callbacks. • Sync and async tests. 8 Network API model
  9. 2.1 Websocket (real time) 9 “ WebSocket is a protocol

    providing full-duplex communications channels over a single TCP connection. The WebSocket protocol was standardized by the IETF as RFC 6455 in 2011, and the WebSocket API in Web IDL is being standardized by the W3C.”
  10. 2.1.1 Websocket (real time) 10

  11. 2.1.2 Websocket. Details 11 • Main way to sync. •

    First implementation we used java-websocket lib. • Problems with its SSL implementation. • Currently we use netty. • OkHttp websockets, asap.
  12. 2.1.3 Websocket. Mutations void handleMessage(String message) { try { JsonObject

    jsonObject = Json.parseJson(message); if (HealthResponse.isHealthResponse(jsonObject)) { ……………… } else if (Response.isValidJsonForObject(jsonObject)) { ……………… } else if (Mutation.isValidJsonForObject(jsonObject)) { receiveMutation((Mutation)Json.fromJson( jsonObject, Mutation.class)); } else { Log.warn("Oh oh! We got a corrupted object on websocket: " + message); } } catch (JsonSyntaxException e) { Log.warn("Discarding a malformed json object on websocket: " + message); } } 12
  13. 2.1.4 Websocket. Real-time example

  14. 2.2 REST 14 • Conventional REST API services for synchronous

    requests. • Secondary (fallback) way to sync. • Using Square OkHttp. • To encourage hunting bugs and API stress tests, we always use production API.
  15. 2.3 Others 15 • Services, manages an API endpoint (Users,

    Lists, Tasks, etc.) • API data models, same structure as API scheme data objects (basic models, just data). • Json serializers/deserializers. Using Gson. Integrity (health) checks. • Callbacks. • Sync/Async unit tests (Junit and mockito) to test requests/ responses.
  16. package com.wunderlist.sync;

  17. 3. Sync • Data models. • Deserializers from the basic

    model. • Cache. • Services that manage data. • Matryoshka (aka Russian doll). • Conflict resolver (included in Matryoshka). This layer manages all business logic of Wunderlist, where we make all sync operations. 17 Data Sync logic
  18. 3.1 Data models Sync data models in this layer manage

    the raw data that we already have from the SDK data models. public class WLUser extends WLApiObject<User> { public static Type collectionType = new TypeToken<List<WLUser>>() {}.getType(); @Persist private boolean isMe; private String pictureUrl; private WLUser(User user) { super(user); } public static WLUser buildFromBaseModel(User user) { return new WLUser(user); } public static WLUser buildFromEmail(String email) { …………… } ……………………………………… }
  19. 3.2 Deserializers public class WLTaskDeserializer extends WLApiObjectDeserializer<Task, WLTask> { @Override

    protected Type getType() { return Task.class; } @Override protected WLTask newInstance(Task baseModel) { return new WLTask(baseModel); } } • Deserializers make the parsing stuff from the raw API model, to the model we are going to use within the app. • Our deserializers are used by Gson to work with the correct scheme. 19
  20. 3.3 Cache 20 • Caches for different models (Tasks, Lists,

    Memberships, etc.) • DataStore interface which our database model (in Android layer) and Cache implement to work on the same way. • Cache has to be filled in a background thread (not UI). • Two dynamic data structures, List and Map.
  21. 3.4 Services • Used to communicate the Android layer with

    the SDK (AppDataController, retrieves API data). public class WLTaskService extends WLService<WLTask, TaskService> { public WLTaskService(Client client) { super(new TaskService(client)); } ………………………… public void getCompletedTasks(final WLList list, final SyncCallback uiCallbacks) { ResponseCallback responseCallback = new ResponseCallback() { @Override public void onSuccess(Response response) { ………………………… uiCallbacks.onSuccess(tasks); } @Override public void onFailure(Response response) { ………………………… uiCallbacks.onFailure(response); } }; service.getCompletedTasksForList(list.getId(), responseCallback); } ………………………… }
  22. 3.5. Matryoshka (aka Russian Doll)

  23. 3.5.1 What is this? 23 • Set of wooden dolls

    of decreasing size placed one inside the other. • Mechanism to properly sync entire model.
  24. 3.5.2 How does it work? 24 • Revision based. •

    When a child is updated the revision changes the whole tree up (eg.: when a Task is created the List's revision is incremented, as well the root).
  25. 3.5.2.1 How does it work?

  26. 3.5.2.2 How does it work? Yay!!! is like a Matryoshka!!!

    so… is recursive ;-) 26
  27. package com.wunderlist.wunderlistandroid;

  28. 4. Android layer 28 • This is the UI +

    Android libs and code. • Wunderlist, just for ICS onwards :-) • Maximized decoupling among UI and business logic.
  29. 4.1 Model View Presenter 29 Presenter (Supervising controller) Passive view

    User events Updates model Updates view State-changes event Model
  30. 4.2 EventBus • We use EventBus to update the UI

    when we have loaded asynchronously our requested data… • … but also when something that we are listen for happens, like a mutation. EventBus is an Android optimised publish/subscribe event bus. A typical use case for Android apps is gluing Activities, Fragments, and background threads together. 30
  31. 4.2.1 EventBus (example) 31 Activity/Fragment A Fragment B Code which

    manages a mutation (do something that the Activity/Fragment A uses to update its UI) (this data has to be used by the Activity/Fragment A) BUS Subscription Data Data Data
  32. 4.3 Loaders • We use loaders to request data to

    our database model/cache. • The loaders, once they finish, return the data to the Activity/Fragment. • The loaders can also subscribe themselves to the EventBus and listen for events. Introduced in Android 3.0, loaders make it easy to asynchronously load data in an activity or fragment. 32
  33. 4.4 Loaders and EventBus (example) 33 Loader (Gets the data

    and does something with it) Activity/Fragment DataSource BUS Subscription Data Data
  34. 4.5 Database • We can not decuple the Android database

    (SQLite) here … • … but we try to make as much abstract as we can the work between data persistence, cache, and API data. 34
  35. 4.6 Others • We use others useful open source libraries

    to manage different stuff, like: - Image management, Square Picasso. - Network request queue, Path PriorityJobQueue. 35
  36. Questions?

  37. Grazie mille!!! +CesarValiente @CesarValiente Gracias!!! Thank you!!!!

  38. License • Content: (cc) 2014-2015 Cesar Valiente Gordo & 6Wunderkinder

    GmbH. Some rights reserved. This document is distributed under the Creative Commons Attribution-ShareAlike 3.0 license, available in http://creativecommons.org/licenses/by-sa/3.0/ • All images used in this presentation belong to their owners.