Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Android Realtime Library

Android Realtime Library

DroidKaigi 2017
「Androidリアルタイム通信アプリ作成Tips」
のスライドです。

Fumihiko Shiroyama

March 08, 2017
Tweet

More Decks by Fumihiko Shiroyama

Other Decks in Technology

Transcript

  1. About Me • Fumihiko Shiroyama • Android App Developer •

    Nikkei Inc. • https://github.com/srym • https://twitter.com/fushiroyama
  2. Realtime Apps' Upsides • Enables Rich User Interaction • Server

    Side Message Pushing • No Pull-To-Refresh
  3. Realtime Apps' Downsides • Connection Problems • Dealing with Poor

    Signal • Hassle with Retransmission • Complex Infrastructure • Proxy Problems etc, etc...
  4. WebSocket GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade

    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
  5. WebSocket GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade

    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
  6. WebSocket Server • https://github.com/theturtle32/WebSocket-Node • $ npm install websocket •

    https://github.com/theturtle32/WebSocket- Node#server-example • $ node server-example.js
  7. WebSocket Server • https://github.com/theturtle32/WebSocket-Node • $ npm install websocket •

    https://github.com/theturtle32/WebSocket- Node#server-example • $ node server-example.js 8IBUFWFS ZPVMJLF
  8. WebSocketListener WebSocketListener listener = new WebSocketListener() { @Override public void

    onOpen(WebSocket ws, Response res) {} @Override public void onClosing(WebSocket ws, int code, String reason) {} @Override public void onClosed(WebSocket ws, int code, String reason) {} @Override public void onFailure(WebSocket ws, Throwable t, Response res) {} @Override public void onMessage(WebSocket ws, final String text) {} @Override public void onMessage(WebSocket ws, ByteString bytes) {} };
  9. WebSocketListener WebSocketListener listener = new WebSocketListener() { @Override public void

    onOpen(WebSocket ws, Response res) {} @Override public void onClosing(WebSocket ws, int code, String reason) {} @Override public void onClosed(WebSocket ws, int code, String reason) {} @Override public void onFailure(WebSocket ws, Throwable t, Response res) {} @Override public void onMessage(WebSocket ws, final String text) {} @Override public void onMessage(WebSocket ws, ByteString bytes) {} };
  10. WebSocketListener @Override public void onMessage(WebSocket ws, final String text) {

    Log.d(TAG, "onMessage(text): " + text); } @Override public void onMessage(WebSocket ws, ByteString bytes) { Log.d(TAG, "onMessage(binary)"); }
  11. WebSocketListener @Override public void onMessage(WebSocket ws, final String text) {

    Log.d(TAG, "onMessage(text): " + text); } @Override public void onMessage(WebSocket ws, ByteString bytes) { Log.d(TAG, "onMessage(binary)"); } 5FYU #JOBSZ
  12. OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder()

    .url("ws://example.com:8080") .build(); final WebSocket webSocket = client.newWebSocket(request, listener); // Trigger shutdown of the dispatcher's executor // so this process can exit cleanly. client.dispatcher().executorService().shutdown(); WebSocket
  13. OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder()

    .url("ws://example.com:8080") .build(); final WebSocket webSocket = client.newWebSocket(request, listener); // Trigger shutdown of the dispatcher's executor // so this process can exit cleanly. client.dispatcher().executorService().shutdown(); WebSocket
  14. OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder()

    .url("ws://example.com:8080") .build(); final WebSocket webSocket = client.newWebSocket(request, listener); // Trigger shutdown of the dispatcher's executor // so this process can exit cleanly. client.dispatcher().executorService().shutdown(); WebSocket
  15. WebSocket OkHttpClient client = new OkHttpClient(); Request request = new

    Request.Builder() .url("ws://example.com:8080") .build(); final WebSocket webSocket = client.newWebSocket(request, listener); // Trigger shutdown of the dispatcher's executor // so this process can exit cleanly. client.dispatcher().executorService().shutdown();
  16. Message public class Message { @SerializedName("sender_id") public final long senderId;

    @SerializedName("message") public final String message; public Message(long senderId, String message) { this.senderId = senderId; this.message = message; } }
  17. send JSON button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v)

    { Message message = new Message(1234, "hi there!"); webSocket.send(gson.toJson(message)); } });
  18. send JSON button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v)

    { Message message = new Message(1234, "hi there!"); webSocket.send(gson.toJson(message)); } });
  19. send JSON button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v)

    { Message message = new Message(1234, "hi there!"); webSocket.send(gson.toJson(message)); } });
  20. receive Message @Override public void onMessage(WebSocket ws, final String text)

    { Message message = gson.fromJson(text, Message.class); Log.d(TAG, "onMessage(text): " + message.toString()); }
  21. receive Message @Override public void onMessage(WebSocket ws, final String text)

    { Message message = gson.fromJson(text, Message.class); Log.d(TAG, "onMessage(text): " + message.toString()); }
  22. WebSocket Tips • WebSocket does NOT have any room, namespace

    mechanism. • WebSocket does NOT support broadcasting out of the box.
  23. WebSocket Tips • WebSocket does NOT have any room, namespace

    mechanism. • WebSocket does NOT support broadcasting out of the box. 4PNF *NQMFNFOUBUJPOTEP
  24. WebSocket Server wsServer.on('request', function(request) { var connection = request.accept(); connection.on('message',

    function(message) { if (message.type === 'utf8') { wsServer.broadcastUTF(message.utf8Data); } else if (message.type === 'binary') { wsServer.broadcastBytes(message.binaryData); } }); }
  25. WebSocket Server wsServer.on('request', function(request) { var connection = request.accept(); connection.on('message',

    function(message) { if (message.type === 'utf8') { wsServer.broadcastUTF(message.utf8Data); } else if (message.type === 'binary') { wsServer.broadcastBytes(message.binaryData); } }); }
  26. WebSocket Server wsServer.on('request', function(request) { var connection = request.accept(); connection.on('message',

    function(message) { if (message.type === 'utf8') { wsServer.broadcastUTF(message.utf8Data); } else if (message.type === 'binary') { wsServer.broadcastBytes(message.binaryData); } }); }
  27. WebSocket Server wsServer.on('request', function(request) { var connection = request.accept(); connection.on('message',

    function(message) { if (message.type === 'utf8') { wsServer.broadcastUTF(message.utf8Data); } else if (message.type === 'binary') { wsServer.broadcastBytes(message.binaryData); } }); }
  28. WebSocket Tips • WebSocket does NOT have any room, namespace

    mechanism. • WebSocket does NOT support broadcasting out of the box. • You can use socket.io for room, namespace, connection retry, fallbacks etc...
  29. Good Points • Simple and Open and Free • Server

    Hook Point • Binary Support • Can Scale • Redis Pub/Sub
  30. Bad Points • No Offline Support • No rooms /

    namespaces • No Broadcasting
  31. Realtime Database 3FNPUF %BUBCBTF -PDBM %BUBCBTF -PDBM %BUBCBTF %BUB 8SJUUFO

    4ZOD 4ZOD 4ZOD $BMMCBDL $BMMCBDL send "hello" "hello" written! "hello" written!
  32. Realtime Database • by Google • Every client looks at

    Local Database • Offline Support! • Data are synced transparently • Handy and robust Security Rule • Don't worry about scaling
  33. Data Structure { "messages": { "one": { "m1": { "name":

    "eclarke", "message": "The relay seems to be malfunctioning.", "timestamp": 1459361875337 }, "m2": { ... }, "m3": { ... } }, "two": { ... }, "three": { ... } } } .FTTBHF 5SFF
  34. Data Structure { "messages": { "one": { "m1": { "name":

    "eclarke", "message": "The relay seems to be malfunctioning.", "timestamp": 1459361875337 }, "m2": { ... }, "m3": { ... } }, "two": { ... }, "three": { ... } } } $IBUSPPN POF
  35. Data Structure { "messages": { "one": { "m1": { "name":

    "shiroyama", "message": "Hello Everyone!", "timestamp": 1459361875337 }, "m2": { ... }, "m3": { ... } }, "two": { ... }, "three": { ... } } } Hello Everyone!
  36. Data Structure { "messages": { "one": { "m1": { "name":

    "shiroyama", "message": "Hello Everyone!", "timestamp": 1459361875337 }, "m2": { ... }, "m3": { ... } }, "two": { ... }, "three": { ... } } } Hello Everyone!
  37. Data Structure { "chats": { "one": { "title": "Room 1",

    "lastMessage": "shiroyama: Hello Everyone!", "timestamp": 1459361875666 }, "two": { ... }, "three": { ... } }, "members": { "one": { "shiroyama": true, "tanaka": true }, "two": { ... }, "three": { ... } }, "messages": { "one": { "m1": { "name": "shiroyama", "message": "Hello Everyone!", "timestamp": 1459361875337 }, "m2": { ... }, "m3": { ... } } } }
  38. Data Structure { "chats": { "one": { "title": "Room 1",

    "lastMessage": "shiroyama: Hello Everyone!", "timestamp": 1459361875666 }, "two": { ... }, "three": { ... } }, "members": { "one": { "shiroyama": true, "tanaka": true }, "two": { ... }, "three": { ... } }, "messages": { "one": { "m1": { "name": "shiroyama", "message": "Hello Everyone!", "timestamp": 1459361875337 }, "m2": { ... }, "m3": { ... } } } } EFOPSNBMJ[JOH
  39. Data Structure { "chats": { "one": { "title": "Room 1",

    "lastMessage": "shiroyama: Hello Everyone!", "timestamp": 1459361875666 }, "two": { ... }, "three": { ... } }, "members": { "one": { "shiroyama": true, "tanaka": true }, "two": { ... }, "three": { ... } }, "messages": { "one": { "m1": { "name": "shiroyama", "message": "Hello Everyone!", "timestamp": 1459361875337 }, "m2": { ... }, "m3": { ... } } } } EFOPSNBMJ[JOH
  40. • https://firebase.google.com/docs/database/security/ • .read, .write, .validate • Predefined Variables •

    https://firebase.google.com/docs/database/security/ securing-data?hl=en#predefined_variables Security Rules
  41. Message @IgnoreExtraProperties public class Message { private String name; private

    String message; private long timestamp; public Message() { } // getters and setters }
  42. Send Message button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v)

    { Message message = new Message(); message.setName(name); message.setMessage(text); message.setTimestamp(timestamp); messages.push().setValue(message); } });
  43. Send Message button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v)

    { Message message = new Message(); message.setName(name); message.setMessage(text); message.setTimestamp(timestamp); messages.push().setValue(message); } });
  44. Send Message button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v)

    { Message message = new Message(); message.setName(name); message.setMessage(text); message.setTimestamp(timestamp); messages.push().setValue(message); } });
  45. Send Message button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v)

    { Message message = new Message(); message.setName(name); message.setMessage(text); message.setTimestamp(timestamp); messages.push().setValue(message); } }); QVTIUPMJTU
  46. { "messages": { "one": { "-Kei_CdbvKoms35KcA8p": { "name": "shiroyama", "message":

    "Hello Everyone!", "timestamp": 1459361875337 }, "m2": { ... }, "m3": { ... } }, "two": { ... }, "three": { ... } } } Hello Everyone! Send Message
  47. { "messages": { "one": { "-Kei_CdbvKoms35KcA8p": { "name": "shiroyama", "message":

    "Hello Everyone!", "timestamp": 1459361875337 }, "m2": { ... }, "m3": { ... } }, "two": { ... }, "three": { ... } } } Hello Everyone! HFOFSBUFECZ QVTI Send Message
  48. Send Message button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v)

    { Message message = new Message(); message.setName(name); message.setMessage(text); message.setTimestamp(timestamp); messages.push().setValue(message); } });
  49. Send Message button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v)

    { Message message = new Message(); message.setName(name); message.setMessage(text); message.setTimestamp(timestamp); messages.push().setValue(message); } }); XSJUF
  50. ChildEventListener listener = new ChildEventListener() { @Override public void onChildAdded(DataSnapshot

    snap, String s) {} @Override public void onChildChanged(DataSnapshot snap, String s) {} @Override public void onChildRemoved(DataSnapshot snap) {} @Override public void onChildMoved(DataSnapshot snap, String s) {} @Override public void onCancelled(DatabaseError error) {} }; messages.addChildEventListener(listener); Receive Message
  51. ChildEventListener listener = new ChildEventListener() { @Override public void onChildAdded(DataSnapshot

    snap, String s) {} @Override public void onChildChanged(DataSnapshot snap, String s) {} @Override public void onChildRemoved(DataSnapshot snap) {} @Override public void onChildMoved(DataSnapshot snap, String s) {} @Override public void onCancelled(DatabaseError error) {} }; messages.addChildEventListener(listener); Receive Message
  52. ChildEventListener listener = new ChildEventListener() { @Override public void onChildAdded(DataSnapshot

    snap, String s) {} @Override public void onChildChanged(DataSnapshot snap, String s) {} @Override public void onChildRemoved(DataSnapshot snap) {} @Override public void onChildMoved(DataSnapshot snap, String s) {} @Override public void onCancelled(DatabaseError error) {} }; messages.addChildEventListener(listener); Receive Message
  53. ChildEventListener listener = new ChildEventListener() { @Override public void onChildAdded(DataSnapshot

    snap, String s) {} @Override public void onChildChanged(DataSnapshot snap, String s) {} @Override public void onChildRemoved(DataSnapshot snap) {} @Override public void onChildMoved(DataSnapshot snap, String s) {} @Override public void onCancelled(DatabaseError error) {} }; messages.addChildEventListener(listener); Receive Message
  54. onChildAdded() @Override public void onChildAdded(DataSnapshot snap, String s) { Message

    message = snap.getValue(Message.class); Log.d(TAG, message.getMessage()); }
  55. onChildAdded() @Override public void onChildAdded(DataSnapshot snap, String s) { Message

    message = snap.getValue(Message.class); Log.d(TAG, message.getMessage()); }
  56. onChildAdded() @Override public void onChildAdded(DataSnapshot snap, String s) { Message

    message = snap.getValue(Message.class); Log.d(TAG, message.getMessage()); } 5ZQF4BGF
  57. • ValueEventListener • Fetch single item / at once •

    ChildEventListener • Fetch from list (add/change/remove/move) • https://firebase.google.com/docs/database/android/ read-and-write Listeners
  58. • Pull-to-Refresh • ValueEvent is triggered at last • Desc

    Ordering • setPrioritiy(timestamp * -1) • or create "desc_timestamp" property Realtime Database Tips
  59. Good Points • Offline Support and Sync • Easy and

    Robust Security Rules • Good Interaction with other Firebase services • Easy to scale
  60. Bad Points • No binary support • No server hook

    point • No source code! • Tricky ordering limitation • Storage and Traffic fee could be expensive
  61. • Alternative to SQLite and Core Data • ORMapper •

    Cross Platform • https://realm.io/products/realm-mobile-database/ Realm Mobile Database
  62. Project's build.gradle buildscript { repositories { jcenter() } dependencies {

    classpath 'com.android.tools.build:gradle:2.2.3' classpath 'io.realm:realm-gradle-plugin:3.0.0' } }
  63. Project's build.gradle buildscript { repositories { jcenter() } dependencies {

    classpath 'com.android.tools.build:gradle:2.2.3' classpath 'io.realm:realm-gradle-plugin:3.0.0' } }
  64. Custom Application public class MyApplication extends Application { @Override public

    void onCreate() { super.onCreate(); Realm.init(this); RealmLog.setLevel(Log.VERBOSE); } }
  65. Custom Application public class MyApplication extends Application { @Override public

    void onCreate() { super.onCreate(); Realm.init(this); RealmLog.setLevel(Log.VERBOSE); } }
  66. Custom Application public class MyApplication extends Application { @Override public

    void onCreate() { super.onCreate(); Realm.init(this); RealmLog.setLevel(Log.VERBOSE); } }
  67. Message Model public class Message extends RealmObject { private String

    name; private String message; private long timestamp; public Message() { } // getters & setters }
  68. Message Model public class Message extends RealmObject { private String

    name; private String message; private long timestamp; public Message() { } // getters & setters }
  69. Message Model public class Message extends RealmObject { private String

    name; private String message; private long timestamp; public Message() { } // getters & setters } OFDFTTBSZ
  70. Message Model public class Message extends RealmObject { private String

    name; private String message; private long timestamp; public Message() { } // getters & setters } OFDFTTBSZ QSPYZUP OBUJWFDPEF
  71. Write to Realm button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View

    v) { Realm realm = Realm.getDefaultInstance(); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { final Message message = realm.createObject(Message.class); message.setName(name); message.setMessage(text); message.setTimestamp(timestamp); } }); } });
  72. Write to Realm button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View

    v) { Realm realm = Realm.getDefaultInstance(); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { final Message message = realm.createObject(Message.class); message.setName(name); message.setMessage(text); message.setTimestamp(timestamp); } }); } });
  73. Write to Realm button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View

    v) { Realm realm = Realm.getDefaultInstance(); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { final Message message = realm.createObject(Message.class); message.setName(name); message.setMessage(text); message.setTimestamp(timestamp); } }); } });
  74. Write to Realm button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View

    v) { Realm realm = Realm.getDefaultInstance(); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { final Message message = realm.createObject(Message.class); message.setName(name); message.setMessage(text); message.setTimestamp(timestamp); } }); } });
  75. Write to Realm button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View

    v) { Realm realm = Realm.getDefaultInstance(); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { final Message message = realm.createObject(Message.class); message.setName(name); message.setMessage(text); message.setTimestamp(timestamp); } }); } });
  76. Write to Realm final Message message = new Message(); message.setName(name);

    message.setMessage(text); message.setTimestamp(timestamp); Realm realm = Realm.getDefaultInstance(); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.copyToRealm(message); } });
  77. or

  78. Write to Realm final Message message = new Message(); message.setName(name);

    message.setMessage(text); message.setTimestamp(timestamp); Realm realm = Realm.getDefaultInstance(); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.copyToRealm(message); } });
  79. Write to Realm final Message message = new Message(); message.setName(name);

    message.setMessage(text); message.setTimestamp(timestamp); Realm realm = Realm.getDefaultInstance(); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.copyToRealm(message); } });
  80. Write to Realm final Message message = new Message(); message.setName(name);

    message.setMessage(text); message.setTimestamp(timestamp); Realm realm = Realm.getDefaultInstance(); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.copyToRealm(message); } });
  81. Read from Realm Realm realm = Realm.getDefaultInstance(); RealmResults<Message> messages =

    realm .where(Message.class) .findAll(); if (!messages.isEmpty()) { for (Message message : messages) { Log.d(TAG, "message: " + message.getMessage()); } }
  82. Read from Realm Realm realm = Realm.getDefaultInstance(); RealmResults<Message> messages =

    realm .where(Message.class) .findAll(); if (!messages.isEmpty()) { for (Message message : messages) { Log.d(TAG, "message: " + message.getMessage()); } }
  83. Read from Realm Realm realm = Realm.getDefaultInstance(); RealmResults<Message> messages =

    realm .where(Message.class) .findAll(); if (!messages.isEmpty()) { for (Message message : messages) { Log.d(TAG, "message: " + message.getMessage()); } }
  84. Read from Realm Realm realm = Realm.getDefaultInstance(); RealmResults<Message> messages =

    realm .where(Message.class) .findAll(); if (!messages.isEmpty()) { for (Message message : messages) { Log.d(TAG, "message: " + message.getMessage()); } }
  85. Read from Realm Realm realm = Realm.getDefaultInstance(); RealmResults<Message> messages =

    realm .where(Message.class) .findAll(); if (!messages.isEmpty()) { for (Message message : messages) { Log.d(TAG, "message: " + message.getMessage()); } }
  86. Change Listener RealmChangeListener<RealmResults<Message>> changListener = new RealmChangeListener<RealmResults<Message>>() { @Override public

    void onChange(RealmResults<Message> element) { for (Message msg : element) { Log.d(TAG, "message: " + msg.getMessage()); } } }; messages.addChangeListener(changListener);
  87. Change Listener RealmChangeListener<RealmResults<Message>> changListener = new RealmChangeListener<RealmResults<Message>>() { @Override public

    void onChange(RealmResults<Message> element) { for (Message msg : element) { Log.d(TAG, "message: " + msg.getMessage()); } } }; messages.addChangeListener(changListener);
  88. Change Listener RealmChangeListener<RealmResults<Message>> changListener = new RealmChangeListener<RealmResults<Message>>() { @Override public

    void onChange(RealmResults<Message> element) { for (Message msg : element) { Log.d(TAG, "message: " + msg.getMessage()); } } }; messages.addChangeListener(changListener);
  89. 3FBMN 0CKFDU 4FSWFS %BUB 8SJUUFO $BMMCBDL send "hello" "hello" written!

    Realm Mobile Platform 3FBMN .PCJMF %BUBCBTF 3FBMN .PCJMF %BUBCBTF
  90. %BUB 8SJUUFO 4ZOD $BMMCBDL send "hello" "hello" written! Realm Mobile

    Platform 3FBMN 0CKFDU 4FSWFS 3FBMN .PCJMF %BUBCBTF 3FBMN .PCJMF %BUBCBTF
  91. %BUB 8SJUUFO 4ZOD 4ZOD 4ZOD $BMMCBDL $BMMCBDL send "hello" "hello"

    written! "hello" written! Realm Mobile Platform 3FBMN 0CKFDU 4FSWFS 3FBMN .PCJMF %BUBCBTF 3FBMN .PCJMF %BUBCBTF
  92. • https://realm.io/docs/realm-object-server/ • macOS • RHEL/CentOS 6 • Ubuntu 16.04

    • AWS/AMI • Developer Edition is FREE Realm Object Server
  93. • Ubuntu 16.04 • curl -s https://packagecloud.io/install/repositories/realm/realm/ script.deb.sh | sudo

    bash • sudo apt-get update • sudo apt-get install realm-object-server-developer • sudo systemctl enable realm-object-server • sudo systemctl start realm-object-server Realm Object Server
  94. Login final SyncCredentials cred = SyncCredentials.usernamePassword(ID, PASSWORD, true); SyncUser.loginAsync(cred, AUTH_URL,

    new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { final SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, REALM_URL).build(); Realm.setDefaultConfiguration(syncConfiguration); realm = Realm.getDefaultInstance(); } @Override public void onError(ObjectServerError error) {} });
  95. Login final SyncCredentials cred = SyncCredentials.usernamePassword(ID, PASSWORD, true); SyncUser.loginAsync(cred, AUTH_URL,

    new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { final SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, REALM_URL).build(); Realm.setDefaultConfiguration(syncConfiguration); realm = Realm.getDefaultInstance(); } @Override public void onError(ObjectServerError error) {} }); $SFBUF6TFS 'JSTU5JNF
  96. Login final SyncCredentials cred = SyncCredentials.usernamePassword(ID, PASSWORD, true); SyncUser.loginAsync(cred, AUTH_URL,

    new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { final SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, REALM_URL).build(); Realm.setDefaultConfiguration(syncConfiguration); realm = Realm.getDefaultInstance(); } @Override public void onError(ObjectServerError error) {} });
  97. Login final SyncCredentials cred = SyncCredentials.usernamePassword(ID, PASSWORD, true); SyncUser.loginAsync(cred, AUTH_URL,

    new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { final SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, REALM_URL).build(); Realm.setDefaultConfiguration(syncConfiguration); realm = Realm.getDefaultInstance(); } @Override public void onError(ObjectServerError error) {} });
  98. Login final SyncCredentials cred = SyncCredentials.usernamePassword(ID, PASSWORD, true); SyncUser.loginAsync(cred, AUTH_URL,

    new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { final SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, REALM_URL).build(); Realm.setDefaultConfiguration(syncConfiguration); realm = Realm.getDefaultInstance(); } @Override public void onError(ObjectServerError error) {} });
  99. Login final SyncCredentials cred = SyncCredentials.usernamePassword(ID, PASSWORD, true); SyncUser.loginAsync(cred, AUTH_URL,

    new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { final SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, REALM_URL).build(); Realm.setDefaultConfiguration(syncConfiguration); realm = Realm.getDefaultInstance(); } @Override public void onError(ObjectServerError error) {} });
  100. Login final SyncCredentials cred = SyncCredentials.usernamePassword(ID, PASSWORD, true); SyncUser.loginAsync(cred, AUTH_URL,

    new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { final SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, REALM_URL).build(); Realm.setDefaultConfiguration(syncConfiguration); realm = Realm.getDefaultInstance(); } @Override public void onError(ObjectServerError error) {} });
  101. Fine-grained Collection Notification public void onChange(RealmResults<Message> collection, OrderedCollectionChangeSet changeSet) {

    OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges(); for (OrderedCollectionChangeSet.Range insertion : insertions) { int index = insertion.startIndex; int length = insertion.length; for (int i = index; i < index + length; i++) { Message message = collection.get(i); Log.d(TAG, "message: " + message.getMessage()); } } } messages.addChangeListener(realmChangeListener);
  102. Fine-grained Collection Notification public void onChange(RealmResults<Message> collection, OrderedCollectionChangeSet changeSet) {

    OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges(); for (OrderedCollectionChangeSet.Range insertion : insertions) { int index = insertion.startIndex; int length = insertion.length; for (int i = index; i < index + length; i++) { Message message = collection.get(i); Log.d(TAG, "message: " + message.getMessage()); } } } messages.addChangeListener(realmChangeListener);
  103. Fine-grained Collection Notification public void onChange(RealmResults<Message> collection, OrderedCollectionChangeSet changeSet) {

    OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges(); for (OrderedCollectionChangeSet.Range insertion : insertions) { int index = insertion.startIndex; int length = insertion.length; for (int i = index; i < index + length; i++) { Message message = collection.get(i); Log.d(TAG, "message: " + message.getMessage()); } } } messages.addChangeListener(realmChangeListener);
  104. Fine-grained Collection Notification public void onChange(RealmResults<Message> collection, OrderedCollectionChangeSet changeSet) {

    OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges(); for (OrderedCollectionChangeSet.Range insertion : insertions) { int index = insertion.startIndex; int length = insertion.length; for (int i = index; i < index + length; i++) { Message message = collection.get(i); Log.d(TAG, "message: " + message.getMessage()); } } } messages.addChangeListener(realmChangeListener);
  105. Let others read/write Realm realm = user.getManagementRealm(); realm.executeTransaction(new Realm.Transaction() {

    @Override public void execute(Realm realm) { Boolean mayRead = true; Boolean mayWrite = null; boolean mayManage = false; PermissionChange change = new PermissionChange(realmUrl, anotherUserID, mayRead, mayWrite, mayManage); realm.insert(change); } });
  106. Let others read/write Realm realm = user.getManagementRealm(); realm.executeTransaction(new Realm.Transaction() {

    @Override public void execute(Realm realm) { Boolean mayRead = true; Boolean mayWrite = null; boolean mayManage = false; PermissionChange change = new PermissionChange(realmUrl, anotherUserID, mayRead, mayWrite, mayManage); realm.insert(change); } }); "MMPX
  107. Let others read/write Realm realm = user.getManagementRealm(); realm.executeTransaction(new Realm.Transaction() {

    @Override public void execute(Realm realm) { Boolean mayRead = true; Boolean mayWrite = null; boolean mayManage = false; PermissionChange change = new PermissionChange(realmUrl, anotherUserID, mayRead, mayWrite, mayManage); realm.insert(change); } }); "TJUJT
  108. Let others read/write Realm realm = user.getManagementRealm(); realm.executeTransaction(new Realm.Transaction() {

    @Override public void execute(Realm realm) { Boolean mayRead = true; Boolean mayWrite = null; boolean mayManage = false; PermissionChange change = new PermissionChange(realmUrl, anotherUserID, mayRead, mayWrite, mayManage); realm.insert(change); } }); %JTBMMPX
  109. Let others read/write Realm realm = user.getManagementRealm(); realm.executeTransaction(new Realm.Transaction() {

    @Override public void execute(Realm realm) { Boolean mayRead = true; Boolean mayWrite = null; boolean mayManage = false; PermissionChange change = new PermissionChange(realmUrl, anotherUserID, mayRead, mayWrite, mayManage); realm.insert(change); } });
  110. Good Points • Offline Support and Sync • Intuitive Query

    & Relation • You can use this on-premises • Developer Edition is FREE • Server Side Hook is Available on Enterprise Edition • Load Balancing is Available on Enterprise Edition
  111. Bad Points • Not really, that much! Great! • No

    server hook on Developer Edition • Realm MAY affect App's Architecture • Live Object is sometimes tricky