In a world where users have ever higher expectations from the apps they use, having data always available, even when the device is offline, has become increasingly important.
In this talk you will learn how thinking "offline first" not only makes your app architecture better but also result in cleaner code and happier users.
I will introduce Realm, a new database for easy persistence, and demonstrate how it enables truly reactive UI's by fitting seamlessly into the standard network stack of Retrofit and RxJava.
Finally we will take a look at the new Realm Mobile Platform, which provides real-time synchronization between devices, enabling features previously out of reach for many development teams.
4. 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.
cm@realm.io
6. cm@realm.io
They all have a model
MVVM
MVC
Flux
Clean
Architecture
MVP
VIPER
ModelView
getData()
data
7. cm@realm.io
You’re doing it wrong
@Override
protected 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);
}
});
}
8. cm@realm.io
You’re doing it right
@Override
protected 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);
}
});
}
11. 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
cm@realm.io
15. Why choose Realm?
cm@realm.io
• A database designed for mobile from the ground up
• NoSQL (but not schemaless)
• Objects all the way down
• Reactive
• Cross-platform
17. What does Realm look like?
cm@realm.io
public class Person extends RealmObject {
@PrimaryKey
private long id;
private String name;
private int age;
// References
private Dog dog;
private RealmList<Cat> cats;
// Methods
// …
}
18. Saving data
cm@realm.io
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);
}
});
21. cm@realm.io
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();
22. cm@realm.io
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.
23. Zero copy / Lazy loading
cm@realm.io
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
}
}
24. Zero copy / Lazy loading
cm@realm.io
• 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.
25. cm@realm.io
Threading model
Model
• 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)
29. 3xR-2: Save data
cm@realm.io
// Load data from the network and insert into Realm
api.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);
}
});
30. 3xR-3: Listen for changes
cm@realm.io
// 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);
}
});
31. 3xR: Benefits
cm@realm.io
• 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.
34. 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.
cm@realm.io
35. What if we removed all that?
cm@realm.io
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.
36. Synchronising a local Realm
cm@realm.io
apply plugin: 'realm-android'
realm {
syncEnabled = true
}
37. cm@realm.io
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
38. cm@realm.io
// Opening a local Realm
RealmConfiguration 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
39. Features
cm@realm.io
• 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.
40. Take aways
cm@realm.io
• Design for offline first - no excuse in 2016
• Repository pattern for decoupling and testability.
• Try Realm - https://realm.io/