Slide 1

Slide 1 text

Engineering Wunderlist for Android. Cesar Valiente

Slide 2

Slide 2 text

Wunderlist, 6Wunderkinder and me

Slide 3

Slide 3 text

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.

Slide 4

Slide 4 text

1. General architecture

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

1.3 What does it mean? 6 • Independent layers. Accessibility. • Clean architecture. • Abstract, easy to change, modular —> interfaces.

Slide 7

Slide 7 text

package com.wunderlist.sdk;

Slide 8

Slide 8 text

2. Sdk • Websocket (real time). • REST. • Services • API data models. • Serializers/deserializers. • Interfaces/callbacks. • Sync and async tests. 8 Network API model

Slide 9

Slide 9 text

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.”

Slide 10

Slide 10 text

2.1.1 Websocket (real time) 10

Slide 11

Slide 11 text

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.

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

2.1.4 Websocket. Real-time example

Slide 14

Slide 14 text

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.

Slide 15

Slide 15 text

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.

Slide 16

Slide 16 text

package com.wunderlist.sync;

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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 { public static Type collectionType = new TypeToken>() {}.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) { …………… } ……………………………………… }

Slide 19

Slide 19 text

3.2 Deserializers public class WLTaskDeserializer extends WLApiObjectDeserializer { @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

Slide 20

Slide 20 text

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.

Slide 21

Slide 21 text

3.4 Services • Used to communicate the Android layer with the SDK (AppDataController, retrieves API data). public class WLTaskService extends WLService { 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); } ………………………… }

Slide 22

Slide 22 text

3.5. Matryoshka (aka Russian Doll)

Slide 23

Slide 23 text

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.

Slide 24

Slide 24 text

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).

Slide 25

Slide 25 text

3.5.2.1 How does it work?

Slide 26

Slide 26 text

3.5.2.2 How does it work? Yay!!! is like a Matryoshka!!! so… is recursive ;-) 26

Slide 27

Slide 27 text

package com.wunderlist.wunderlistandroid;

Slide 28

Slide 28 text

4. Android layer 28 • This is the UI + Android libs and code. • Wunderlist, just for ICS onwards :-) • Maximized decoupling among UI and business logic.

Slide 29

Slide 29 text

4.1 Model View Presenter 29 Presenter (Supervising controller) Passive view User events Updates model Updates view State-changes event Model

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

4.4 Loaders and EventBus (example) 33 Loader (Gets the data and does something with it) Activity/Fragment DataSource BUS Subscription Data Data

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Questions?

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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.