+ All Categories
Home > Technology > Painless Persistence with Realm

Painless Persistence with Realm

Date post: 22-Jan-2018
Category:
Upload: christian-melchior
View: 564 times
Download: 3 times
Share this document with a friend
41
Painless Persistence with Realm Foo Café Stockholm Christian Melchior @chrmelchior [email protected]
Transcript

Painless Persistence with RealmFoo Café StockholmChristian Melchior @chrmelchior

[email protected]

Design for offline• A better USER EXPERIENCE!

• You always have something to show to the user.

• Reduce network requests and data transferred.

• Saves battery.

• It is 2016.

[email protected]

[email protected]

Offline architecture?MVVM

MVC

Flux

Clean Architecture

?

?? ?

??

?

?

??

?? ??

??

MVPVIPER

[email protected]

They all have a modelMVVM

MVC

Flux

Clean Architecture

MVPVIPER

ModelView

getData()

data

[email protected]

You’re doing it wrong@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Setup initial views setContentView(R.layout.activity_main);

// Load data from the REST API and show it myApi = retrofit.create(NYTimesService.class); myApi.topStories("home", "my-key") .subscribe(new Action1<List<NYTimesStory>>() { @Override public void call(List<NYTimesStory> response) { showList(response); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { showError(throwable); } });}

[email protected]

You’re doing it right@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Setup initial views setContentView(R.layout.activity_main);

// Load data from the Model and show it model = ((MyApplication) getApplicationContext()).getModel(); model.getTopStories() .subscribe(new Action1<List<NYTimesStory>>() { @Override public void call(List<NYTimesStory> response) { showList(response); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { showError(throwable); } });}

[email protected]

Encapsulate data

ModelView

Cache

Database

Network

[email protected]

Repository pattern

ModelView

Cache

Database

Network

Repository

Business rules

Creating/fetching data

Repository pattern• Repository only have CRUD methods.

• Create() • Read() • Update() • Delete()

• Model and repository can be tested separately.

• http://hannesdorfmann.com/android/evolution-of-the-repository-pattern

[email protected]

[email protected]

Designing for offline

Repository… DatastoregetData()

Observable<Data>()

NetworkUpdate?

Fetch

Repository… DatastoregetData()

Observable<Data>()

NetworkUpdate?

Save

Designing for offline• Encapsulate data access.

• The datastore is “Single Source of Truth”.

• Everything is asynchronous. • Observer pattern • RxJava • EventBus

• Testing becomes easier.

[email protected]

Realm

[email protected]

A Replacement for SQLite and ORM’s

Why choose Realm?

[email protected]

• A database designed for mobile from the ground up

• NoSQL (but not schemaless)

• Objects all the way down

• Reactive

• Cross-platform

What does Realm look like?

[email protected]

public class Person extends RealmObject { @PrimaryKey private long id; private String name; private int age; // References private Dog dog; private RealmList<Cat> cats;

// Methods

// …}

Saving data

[email protected]

realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // Create Object directly Person person = realm.createObject(Person.class); person.setId(1); person.setName("Young Person"); person.setAge(14); // Or insert a normal Java object Person p = new Person(1, "Young Person", 14); realm.insertOrUpdate(p); }});

Queries

[email protected]

// Synchronous queries RealmResults<Person> results = realm.where(Person.class) .between("age", 7, 9) .beginsWith("name", "Person") .isNull("dog") .findAll();

// Asynchronous queries RealmResults<Person> results = realm.where(Person.class) .equalTo("dogs.name", "Fido") .findAllAsync();results.addChangeListener(new RealmChangeListener<RealmResults<Person>>() { @Override public void onChange(RealmResults<Person> results) { showUI(results); }});

[email protected]

References are first class

SELECT person.name, dog.name FROM person

INNER JOIN dog ON person.dog_id = dog.id

WHERE person.name = ‘Frank’

RealmResults<Person> results; results = realm.where(Person.class) .equalTo("name", "Frank") .findAll(); String name = results.first() .getDog().getName();

[email protected]

References are first class• No JOIN’s. • No object-relational impedance mismatch. • ID’s not required for references. • Navigating the object graph is as fast as

following normal references.

Zero copy / Lazy loading

[email protected]

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}}

Zero copy / Lazy loading

[email protected]

• Realm is always Single Source of Truth • RealmResults ~= Typesafe Cursor • Memory efficient • No need for LIMIT and Load More buttons

Caveat • Consider caching values if used in a loop.

[email protected]

Threading modelModel • MVCC • Each thread has a consistent

view. • Thread confined objects.

API’s • Change listeners

Model • Your on your own

API’s • Loaders • ContentObservers • ContentProviders • RxJava (3rd party) • SQLBrite (3rd party)

Implementing offline-first

[email protected]

In 3 easy steps

3xR: Realm, Retrofit and RxJava

[email protected]

buildscript { dependencies { classpath 'io.realm:realm-gradle-plugin:2.0.2' } } dependencies { compile 'com.google.code.gson:gson:2.7' compile 'io.reactivex:rxjava:1.2.1' compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.squareup.retrofit2:converter-gson:2.1.0' compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' }

apply plugin: 'realm-android'

3xR-1: Setup Retrofit

[email protected]

public interface NYTimesService { @GET("svc/topstories/v1/{section}.json") Observable<List<NYTimesStory>> topStories( @Path("section") String section, @Query(value = "api-key", encoded = true) String apiKey);}

Retrofit retrofit = new Retrofit.Builder() .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .baseUrl("http://api.nytimes.com/") .build();NYTimesService api = retrofit.create(NYTimesService.class);

3xR-2: Save data

[email protected]

// Load data from the network and insert into Realmapi.topStories("home", apiKey).asObservable().subscribe(new Action1<List<NYTimesStory>>() { @Override public void call(final List<NYTimesStory> stories) { Realm realm = Realm.getDefaultInstance(); realm.executeTransaction(r -> { r.insertOrUpdate(stories); }); realm.close(); }}, new Action1<Throwable>() { @Override public void call(Throwable throwable) { retryOrHandleError(throwable); }});

3xR-3: Listen for changes

[email protected]

// Realm Observables never complete, updating your UI if anything changes Realm realm = Realm.getDefaultInstance();realm.where(NYTimesStory.class) .findAllSortedAsync("published_date", Sort.DESCENDING) .asObservable() .subscribe(new Action1<RealmResults<NYTimesStory>>() { @Override public void call(RealmResults<NYTimesStory> stories) { updateUI(stories); } });

3xR: Benefits

[email protected]

• Offline first with very few lines of code.

• Decouple Network and UI .

• Reactive UI: No matter where the update come from, your UI will reflect it.

Realm Mobile Platform

[email protected]

Automatic synchronisation between devices

Synchronising changes• Server always wins

• Easy • Client not allowed to write or risk loosing changes.

• Client can win • Hard • Complexity explosion • Changes needs to be tracked and merged.

[email protected]

What if we removed all that?

[email protected]

Native Realm Object

Realm Object Server

Only Realm

Native object

JSON

Backend object

SQL

Backend object

JSON

Native object

SQLite/CoreData SQLite/CoreData

e.g. Firebase, Parse, etc.

Synchronising a local Realm

[email protected]

apply plugin: 'realm-android'realm { syncEnabled = true}

[email protected]

SyncCredentials creds = SyncCredentials.usernamePassword(username, password, createUser);SyncUser.loginAsync(creds, "https://my.server/auth", new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { openRealm(user); } @Override public void onError(ObjectServerError error) { handleError(error); }});

Synchronising a local Realm

[email protected]

// Opening a local RealmRealmConfiguration config = new RealmConfiguration.Builder().build();Realm realm = Realm.getInstance(config);// Opening a synchronized Realm SyncUser user = login();String url = "realm://my.server/~/default"; SyncConfiguration config = new SyncConfiguration.Builder(user, url).build();Realm realm = Realm.getInstance(config);

Synchronising a local Realm

Features

[email protected]

• Automatic conflict resolution using Operational Transform.

• Offline first.

• Same reactive pattern on the Client and the Server.

• Node.js API on the server side for integration with other DB’s or API’s.

Take aways

[email protected]

• Design for offline first - no excuse in 2016

• Repository pattern for decoupling and testability.

• Try Realm - https://realm.io/

[email protected]

Questions?

Christian Melchior [email protected] www.realm.io @chrmelchior


Recommended