painless persistence in a disconnected world

55
Painless Persistence on Android Christian Melchior @chrmelchior [email protected]

Upload: christian-melchior

Post on 23-Feb-2017

934 views

Category:

Technology


3 download

TRANSCRIPT

Page 1: Painless Persistence in a Disconnected World

Painless Persistence on Android

Christian Melchior @chrmelchior [email protected]

Page 2: Painless Persistence in a Disconnected World
Page 3: Painless Persistence in a Disconnected World
Page 4: Painless Persistence in a Disconnected World
Page 5: Painless Persistence in a Disconnected World

Why design for offline?

• A better USER EXPERIENCE!

• You always have something to show the user

• Reduce network requests and data transferred

• Saves battery

Page 6: Painless Persistence in a Disconnected World

Designing for Offline

Page 7: Painless Persistence in a Disconnected World

Offline architectureMVVM

MVP

MVC

VIPER

Flux

Clean Architecture

?

?? ?

??

?

?

??

?? ??

??

Page 8: Painless Persistence in a Disconnected World

They all have a modelMVVM

MVP

MVC

VIPER

Flux

Clean Architecture

ModelView

getData()

data

Page 9: Painless Persistence in a Disconnected World

You’re doing it wrong!@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Setup initial views setContentView(R.layout.activity_main); // Load data and show it Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(JacksonConverterFactory.create()) .baseUrl("http://api.nytimes.com/") .build(); service = retrofit.create(NYTimesService.class); service.topStories("home", "my-key").enqueue(new Callback<NYTimesResponse<List<NYTimesStory>>>() { @Override public void onResponse(Response<NYTimesResponse<List<NYTimesStory>>> response, Retrofit retrofit) { showList(response); } @Override public void onFailure(Throwable t) { showError(t); } }); }

Page 10: Painless Persistence in a Disconnected World

You’re doing it right!@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Setup initial views setContentView(R.layout.activity_main); // Load data and show it Model model = ((MyApplication) getApplicationContext()).getModel(); model.getTopStories(new Observer() { @Override public void update(Observable observable, NYTimesResponse<List<NYTimesStory>> data) { showList(data); } }); }

Page 11: Painless Persistence in a Disconnected World

Encapsulate data

ModelView

Cache

Database

Network

Page 12: Painless Persistence in a Disconnected World

Repository pattern

ModelView

Cache

Database

Network

Repository

Business rules

Creating/fetching data

Page 13: Painless Persistence in a Disconnected World

Repository pattern

• Repository only has CRUD methods:

• Create()

• Read()

• Update()

• Delete()

• Model and Repository can be tested separately.

Page 14: Painless Persistence in a Disconnected World

Designing for offline

Repository… DatastoregetData()

Observable<Data>()

NetworkUpdate?

Save

Page 15: Painless Persistence in a Disconnected World

Designing for offline• Encapsulate data access

• The datastore is single-source-of-truth

• Everything is asynchronous

• Observer pattern

• RxJava

• EventBus

• Testing becomes easier

Page 16: Painless Persistence in a Disconnected World

Persisting data

Page 17: Painless Persistence in a Disconnected World

File system

• Define hierarchy using folders

• No help from the framework

• Use cases: Images, JSON blobs

Page 18: Painless Persistence in a Disconnected World

File system

// App internal filescontext.getFilesDir(); context.getCacheDir(); // App external filescontext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS); context.getExternalCacheDir(); // SD CardEnvironment.getExternalStorageDirectory();

Page 19: Painless Persistence in a Disconnected World

SharedPreferences

• Key-value store

• Simple XML file

• Use cases: Settings, Key/Value data

• Simple API’s and easy to use

Page 20: Painless Persistence in a Disconnected World

SharedPreferences

// Save dataSharedPreferences pref = getPreferences(MODE_PRIVATE); SharedPreferences.Editor editor = pref.edit(); editor.putBoolean("key", true); editor.commit();

// Read data boolean data = pref.getBoolean("key", false);

Page 21: Painless Persistence in a Disconnected World

Databases

• Use cases: Structured data sets

• Advanced composing and query capabilities

• Complex API’s

Page 22: Painless Persistence in a Disconnected World

SQLite vs. the World

• SQLite (Relational)

• Realm (Object store)

• Firebase (Document oriented)

• Couchbase Lite (Document oriented)

• Parse (Document oriented)

Page 23: Painless Persistence in a Disconnected World

The Relational Model

Page 24: Painless Persistence in a Disconnected World

Relational data

Page 25: Painless Persistence in a Disconnected World

Relational data

Page 26: Painless Persistence in a Disconnected World

BCNF

Relational data

Page 27: Painless Persistence in a Disconnected World

Relational data

Page 28: Painless Persistence in a Disconnected World

m:n?

Relational data

Page 29: Painless Persistence in a Disconnected World

Relational data

Page 30: Painless Persistence in a Disconnected World

SELECT owner.name, dog.name, city.name FROM owner

INNER JOIN dog ON owner.dog_id = dog.id

INNER JOIN city ON owner.city_id = city.id

WHERE owner.name = 'Frank'

SQLite

Page 31: Painless Persistence in a Disconnected World

String query = "SELECT " + Owner.NAME + ", " + Dog.NAME + ", " + City.NAME + " FROM " + Owner.TABLE_NAME

+ " INNER JOIN " + Dog.TABLE_NAME + " ON " + Owner.DOG_ID + " = " + Dog.ID

+ " INNER JOIN " + City.TABLE_NAME + " ON " + Owner.CITY_ID + " = " + City.ID

+ " WHERE " + Owner.NAME = "'" + escape(queryName) + "'";

SQLite

Page 32: Painless Persistence in a Disconnected World
Page 33: Painless Persistence in a Disconnected World

“Lets use an ORM”

Abstract the problem away

Page 34: Painless Persistence in a Disconnected World

–Joel Spolsky

“All non-trivial abstractions, to some degree, are leaky.”

Page 35: Painless Persistence in a Disconnected World

Solved problem?• ActiveRecord

• Androrm

• Blurb

• Cupboard

• DBFlow

• DBQuery

• DBTools

• EasyliteOrm

• GreenDAO

• Ollie

• Orman

• OrmLite

• Persistence

• RushORM

• Shillelagh

• Sprinkles

• SquiDB

• SugarORM

Page 36: Painless Persistence in a Disconnected World

Realm?

Page 37: Painless Persistence in a Disconnected World

Object Store

A

B C

D E F

G

VS

Page 38: Painless Persistence in a Disconnected World

x

z

y

x y z

SELECT table1.x, table2.y, table3.z

FROM table1

INNER JOIN table2 ON table1.table2_id = table1.id

INNER JOIN table3 ON table1.table3_id = table3.id

References in SQL

Page 39: Painless Persistence in a Disconnected World

A

B C

D E F

G

Realm.getA().getC().getF()

Object Store references

Page 40: Painless Persistence in a Disconnected World

Zero-copy

 Person  {  • name  =  Tommy  • age  =  8  • dog  =  {  

• name  =  Lassie          }    }

 Person  {  • name  =  Tommy  • age  =  8  • dog  =  {  

• name  =  Lassie          }    }

 Person  {  • name  =  Tommy  • age  =  8  • dog  =  {  

• name  =  Lassie          }    }

 PersonProxy  {  • name  • age  • dog    

 }

 PersonProxy  {  • name  • age  • dog    

 }

 Person  {  •name  =  Tommy  •age  =  8  •dog  =  {  

•name  =  Lassie          }    }

Realm ORM Realm SQLite

Page 41: Painless Persistence in a Disconnected World

Benchmarks

http://static.realm.io/downloads/java/android-benchmark.zip

1.09%2.26%

3.79%4.55%

22.85%

13.37%

1% 1% 1% 1% 1% 1%

0%

5%

10%

15%

20%

25%

BatchWrite% SimpleWrite% SimpleQuery% FullScan% Sum% Count%

Speedu

p&vs.&SQLite&

Tests&/&1000&objects&

Realm%

SQLite%

Page 42: Painless Persistence in a Disconnected World

SQLite vs. Realm• Part of Android

• Relational data model

• Based on SQL

• Public Domain

• One of the most tested pieces of software

• Need to map between SQL and Java objects (manually or ORM).

• Foreign collections is an unsolvable problem.

• Complex API’s

• Objects all the way down

• Zero copy architecture

• Cross-platform

• Supports encryption out of the box

• Open source*

• Custom query language

• Will add ~2500 methods + 800 kB of native code.

• Still in beta

Page 43: Painless Persistence in a Disconnected World

“Talk is cheap. Show me the code.”

–Linus Torvalds

Page 44: Painless Persistence in a Disconnected World

Adding Realm// ./build.gradlebuildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.0.0-alpha1' classpath 'io.realm:realm-gradle-plugin:0.86.0' } }

// ./app/build.gradleapply plugin: 'com.android.application'apply plugin: 'realm'

Page 45: Painless Persistence in a Disconnected World

Models and Schemapublic class Person extends RealmObject { private String name; private int age; private Dog dog; private RealmList<Cat> cats;

// Autogenerated getters/setters}

public class Cat extends RealmObject { private String name;

// Autogenerated getters/setters}

public class Dog extends RealmObject { private String name;

// Autogenerated getters/setters}

Page 46: Painless Persistence in a Disconnected World

Getting an Realm instance

// Default configuration. Schema is automatically detected RealmConfiguration config = new RealmConfiguration.Builder(context).build(); Realm.setDefaultConfiguration(config);

Realm realm = Realm.getDefaultInstance();

Page 47: Painless Persistence in a Disconnected World

Create objects - Realm// Create and set persisted objectsrealm.beginTransaction(); Person person = realm.createObject(Person.class); person.setName("Young Person"); person.setAge(14); realm.commitTransaction();

// Copy java objectsPerson person = new Person(); person.setName("Young Person"); person.setAge(14); realm.beginTransaction(); realm.copyToRealm(person); realm.commitTransaction();

• Extend RealmObject

• POJO: Plain Old Java Object

Page 48: Painless Persistence in a Disconnected World

Transactions - Realm

// Simple writesrealm.beginTransaction(); // ...realm.commitTransaction();

// Automatically handle begin/commit/cancelrealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // ... } });

Page 49: Painless Persistence in a Disconnected World

QueriesRealmResults<Person> results = realm.where(Person.class) .equalTo("age", 99) .findAll();

RealmResults<Person> results = realm.where(Person.class) .equalTo("cats.name", “Tiger") .findAll();

RealmResults<Person> results = realm.where(Person.class) .beginsWith("name", “John") .findAllAsync();

results.addChangeListener(new RealmChangeListener() { @Override public void onChange() { showResults(results); } });

• Fluent queries

• Semi-typesafe

• Easy async

• Always up-to-date

Page 50: Painless Persistence in a Disconnected World

Network data// Use Retrofit to parse objectsRetrofit retrofit = new Retrofit.Builder() .addConverterFactory(JacksonConverterFactory.create()) .baseUrl("http://api.nytimes.com/") .build(); MyRestApi service = retrofit.create(MyRestApi.class); final Data data = service.getData(); // Copy all data into Realmrealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.copyToRealmOrUpdate(data); } });

Page 51: Painless Persistence in a Disconnected World

RxJava (Realm)

Observable<Realm> observableRealm = realm.asObservable();

Observable<RealmResults<Person>> results = realm.where(Person.class) .beginsWith("name", “John") .findAllAsync() .asObservable();

Observable<Person> results = realm.where(Person.class).findFirst().asObservable();

Observable<RealmQuery> results = realm.where(Person.class).asObservable();

Page 52: Painless Persistence in a Disconnected World

Examplehttps://github.com/realm/realm-java/tree/cm/offline-

newsreader/examples/newsreaderExample

Page 53: Painless Persistence in a Disconnected World

Take aways

• Not everyone is on Wifi or 4G networks

• Encapsulate data access

• Designing for offline gives a a better USER EXPERIENCE!

Page 54: Painless Persistence in a Disconnected World

Resources• Repository Pattern: http://martinfowler.com/eaaCatalog/

repository.html

• Design for offline: https://plus.google.com/+AndroidDevelopers/posts/3C4GPowmWLb

• MVP example: https://www.code-labs.io/codelabs/android-testing/#0

• Optimizing network requests: https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE

• Realm : https://realm.io/docs/java/latest/

Page 55: Painless Persistence in a Disconnected World

Questions?@chrmelchior [email protected]

We are hiring https://realm.io/jobs/