REALM.IO NOSQL FOR MOBILE
Mitchell Tilbrook▸ Twitter: @sir_tilbrook▸ Mobile Engineer @ Seatfrog▸ Android, iOS, Xamarin
▸ Recording & Editing for 7+ meet-ups▸ ANZCoders: https://www.youtube.com/c/
ANZCoders▸ Android Sydney: http://bit.ly/2dvqk2t
WHY?
1. Not always FAST2. Threading is hard
3. Writes can block reads4. Stringly typed schema
5. Object Mapping6. Migrations
DATA MODELS
ANDROID MODELSpublic class Person extends RealmObject { private String name; private int age;
// Getters and Setters}
OR
ANDROID INTERFACE MODELS@RealmClasspublic class Person implements RealmModel { private String name; private int age;
// Getters and Setters …}
// with RealmObjectperson.isValid();// With RealmModelRealmObject.isValid(user);
PROPERTY ATTRIBUTES
ANDROID PRIMARY KEYpublic class Person extends RealmObject { @PrimaryKey private int id; private String name; private Int age;
// Getters and Setters}
REQUIREDpublic class Person extends RealmObject { @Required private String name; @Required private Int age;
// Getters and Setters}
INDEXINGpublic class Person extends RealmObject { @Index private String name; private int age;
// Getters and Setters}
DON'T PERSIST PROPERTIESpublic class User extends RealmObject { private String email; @Ignore private String sessionToken;
// Getters and Setters}
CUSTOM MODEL LOGICpublic class Person extends RealmObject { private String name; private int age;
public String getName() { return name.toUpperCase(); }
public String getNameAndAge() { return name + " is " + age + " old"; }
public void setName(String newName) { name = newName.toUpperCase(); }}
QUERYING// Gets All the Users RealmQuery<Person> query = realm.where(Person.class);
QUERYING// Gets All the Users RealmQuery<Person> query = realm.where(Person.class);
// Add query conditions:query.equalTo("name", "John");query.or().equalTo("name", "Peter");
// Execute the query:RealmResults<Person> result1 = query.findAll();
// Or alternatively do the same all at once (the "Fluent interface"):RealmResults<Person> result2 = realm.where(Person.class) .equalTo("name", "John") .or() .equalTo("name", "Peter") .findAll();
MANY-TO-ONEpublic class Person extends RealmObject { // .... private Dog dog;
// Getters and Setters }
MANY-TO-MANYpublic class Person extends RealmObject { // .... private RealmList<Dog> dogs;
// Getters and Setters }
RECURSIVE RELATIONSHIPSpublic class Person extends RealmObject { public String name; public RealmList<Person> friends; // Other fields…}
public class Person extends RealmObject { public String name; public Person father; // Other fields…}
LINKED QUERIESpublic class Person extends RealmObject { private String id; private String name; private RealmList<Dog> dogs; // getters and setters}
public class Dog extends RealmObject { private String id; private String name; private String color; // getters and setters}
LINKED QUERIES
LINKED QUERIESRealmResults<Person> persons = realm .where(Person.class) .equalTo("dogs.color", "Brown") .findAll();
QUERYING IS LAZYRealmResults<Person> result = realm .where(Person.class) .findAll();
READING LAZY DATAPerson person = results.get(1);
String name = person.getName();
ASYNC QUERYINGprivate RealmChangeListener callback = new RealmChangeListener() { @Override public void onChange(RealmResults<Person> results) { // called once the query complete and on every update }};
public void onStart() { RealmResults<Person> result = realm.where(Person.class).findAllAsync(); result.addChangeListener(callback);}
AUTO UPDATINGRealmResults<Person> peoples = realm.where(Person.class).findAll();peoples.size(); // => 0
addRandomPerson();
peoples.addChangeListener( results -> { // results and peoples point are both up to date results.size(); // => 1 peoples.size(); // => 1});
CHANGE EVENTS BUBBLERealmResults<Person> peoples = realm.where(Person.class).findAll();
peoples.addChangeListener( results -> { /* ... */ } );
realm.addChangeListener( results -> { /* ... */ } );
FINE GRAIN CHANGE LISTENERS
SAVING UNMANAGED OBJECTSPerson obj = new Person();obj.setId(42);obj.setName("Fish");
// Obtain a Realm instanceRealm realm = Realm.getDefaultInstance();// Save to diskrealm.beginTransaction();managedPerson = realm.copyToRealmOrUpdate(obj);realm.commitTransaction();
SAVING MANAGED OBJECTS// Obtain a Realm instanceRealm realm = Realm.getDefaultInstance();realm.beginTransaction();Person person = realm.createObject(Person.class);person.setName("Mitchell");person.setAge(27);realm.commitTransaction();
ROLLBACK FAILED TRANSACTIONStry { realm.beginTransaction(); Person person = realm.createObject(Person.class); person.setName("Mitchell"); person.setAge(27); realm.commitTransaction();} catch(Exception ex) { // Rollback realm.cancelTransaction();}
RECOMMENDED TRANSACTION STYLEouterRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Person person = realm.createObject(Person.class); person.setName("Mitchell"); person.setAge(27); }});
ASYNCHRONOUS TRANSACTIONSrealm.executeTransactionAsync({ // ...}, new Realm.Transaction.OnSuccess() { @Override public void onSuccess() { // Transaction was a success. }}, new Realm.Transaction.OnError() { @Override public void onError(Throwable error) { // Transaction failed and was automatically canceled. }});
DELETE TRANSACTIONSrealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { person.getDog().deleteFromRealm(); person.deleteFromRealm(); }});
AGGREGATIONRealmResults<User> results = realm.where(User.class).findAll();long sum = results.sum("age").longValue();long min = results.min("age").longValue();long max = results.max("age").longValue();double average = results.average("age");
long matches = results.size();
ENCRYPTION// Totally not safe keyval key = new byte[64];new SecureRandom().nextBytes(key);val config = new RealmConfiguration.Builder() .encryptionKey(key) .build();
// Open the Realm with encryption enabledval realm = Realm.getInstance(config);
DATA MIGRATIONS
JUST DELETE ITRealmConfiguration config = new RealmConfiguration.Builder() .deleteRealmIfMigrationNeeded() .build()
MIGRATIONSRealmConfiguration config = new RealmConfiguration.Builder() .schemaVersion(2) // Must be bumped when the schema changes .migration(new MyMigration()) // Migration to run instead of throwing an exception .build()
MIGRATIONSRealmMigration migration = new RealmMigration() { @Override public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { RealmSchema schema = realm.getSchema();
if (oldVersion == 0) { schema.create("Person") .addField("name", String.class) .addField("age", int.class); oldVersion++; }
if (oldVersion == 1) { schema.get("Person") .addField("id", long.class, FieldAttribute.PRIMARY_KEY) .addRealmObjectField("favoriteDog", schema.get("Dog")) .addRealmListField("dogs", schema.get("Dog")); oldVersion++; } }}
REALM SETUP
ANDROID SETUPbuildscript { repositories { jcenter() } dependencies { classpath "io.realm:realm-gradle-plugin:2.0.0" }}
apply plugin: 'realm-android'
ANDROID INITpublic class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Realm.init(this); RealmConfiguration config = new RealmConfiguration.Builder() .build(); Realm.setDefaultConfiguration(config) }}
SWIFT & OBJECTIVE-C▸ https://realm.io/docs/swift/latest/#installation▸ https://realm.io/docs/objc/latest/#installation
REALM BROWSER
REALM MOBILE PLATFORM
REALM USER SEGREGATION
DRAWBACKS▸ Thread confined*▸ Large binary
▸ Connections need to be closed
ANDROID SIZE COST
IOS SIZE COST
INSPECT REALM ON ANDROID
Realm.init(this);
Stetho.initialize( Stetho.newInitializerBuilder(this) .enableDumpapp( Stetho.defaultDumperPluginsProvider(this) ) .enableWebKitInspector( RealmInspectorModulesProvider .builder(this) .build() ) .build());
ANDROID LIBRARY INTEGRATIONS▸ JSON ( GSON, Moshi )
▸ RxJava▸ Retrofit
RXJAVA SUPPORTrealm.where(Person.class) .findAllAsync() .asObservable();
RXJAVA REALM SCHEDULERfun Schedulers.newRealmThread(): Scheduler { val backgroundThread = HandlerThread( "RxRealm-BackgroundThread", THREAD_PRIORITY_BACKGROUND ) backgroundThread.start() val backgroundLooper = backgroundThread.looper return AndroidSchedulers.from(backgroundLooper)}
compile "io.reactivex:rxandroid:$rx_android_version"
PRODUCTION READY?
YES
QUESTIONS?
Mitchell Tilbrook ! @SIR_TILBROOK " GITHUB.COM/MARUKAMI