Painless Persistence with Realm
-
Upload
christian-melchior -
Category
Technology
-
view
564 -
download
3
Transcript of Painless Persistence with Realm
Painless Persistence with RealmFoo Café StockholmChristian Melchior @chrmelchior
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.
Offline architecture?MVVM
MVC
Flux
Clean Architecture
?
?? ?
??
?
?
??
?? ??
??
MVPVIPER
They all have a modelMVVM
MVC
Flux
Clean Architecture
MVPVIPER
ModelView
getData()
data
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); } });}
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); } });}
Encapsulate data
ModelView
Cache
Database
Network
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
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.
Why choose Realm?
• 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?
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
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
// 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); }});
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();
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
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
• 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.
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)
3xR: Realm, Retrofit and RxJava
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
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
// 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
// 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
• 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
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.
What if we removed all that?
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
apply plugin: 'realm-android'realm { syncEnabled = true}
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
// 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
• 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
• Design for offline first - no excuse in 2016
• Repository pattern for decoupling and testability.
• Try Realm - https://realm.io/
Questions?
Christian Melchior [email protected] www.realm.io @chrmelchior