สำหรับเรื่อง GreenDAO สามารถไปอ่านเพิ่มเติมได้ที่ Martroutine.com และ Devahoy.com เขียนไว้ได้ละเอียดมากครับ
http://www.slideshare.net/ChristianMelchior/realm-building-a-mobile-database
Realm คืออะไร ?
Realm เป็น mobile database มีจุดเด่นคือการใช้ engine ในการจัดเก็บข้อมูลของตัวเอง ที่ออกแบบให้มีความเรียบง่าย(Simplicity) และเร็ว(Speed) หรือก็คือไม่ได้ใช้ SQLite เหมือนกับ database ตัวอื่นๆ นั่นเอง
นอกจากนี้ Realm ยังเคลมว่าตัวเองเร็วกว่า database เจ้าอื่นๆ ด้วยนะ ไม่เชื่อลองไปดู benchmarks สิ ไม่เพียงเท่านั้น มันยัง cross platform อีกต่างหาก support ทั้ง Objective-C, Swift, React Native และ Java(Android) เจ๋งสุดๆ ความเจ๋งของ Realm ยังมีอีกหลายข้อ ลองไปอ่านเพิ่มเติมได้ที่เว็บ Realm ได้เลย
Prerequisites
- Android Studio เวอร์ชัน 1.5.1 ขึ้นไป
- Android SDK เวอร์ชันล่าสุด
- JDK เวอร์ชัน 7 ขึ้นไป
- รองรับ Android API Level 9 (Android 2.3 Gingerbread) ขึ้นไป
- Android Studio เวอร์ชัน 1.5.1 ขึ้นไป
- Android SDK เวอร์ชันล่าสุด
- JDK เวอร์ชัน 7 ขึ้นไป
- รองรับ Android API Level 9 (Android 2.3 Gingerbread) ขึ้นไป
การติดตั้ง
การติดตั้ง Realm ตั้งแต่ version 0.88.0 ขึ้นไป จะต้องติดตั้งด้วย Gradle plugin โดยจะมีขั้นตอนการติดตั้ง 2 ขั้นตอน (Version ณ ที่เขียนอยู่ตอนนี้ คือ 0.86.0 0.89.0 นะครับ)
Step 1:
เพิ่ม class path ใน build.gradle ของ project
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:0.89.0"
}
}
Step 2:
เพิ่ม realm-android plugin ไว้บนสุดใน build.gradle ของ application
apply plugin: 'realm-android'
การใช้งาน
ก่อนอื่นต้องออกตัวก่อนว่า ตัวผมเองก็ไม่เคยใช้งาน Realm มาก่อน เพราะฉะนั้นในบทความนี้เนื้อหาก็คงเป็นในลักษณะของการแนะนำคร่าวๆ ก่อนนะครับ ซึ่งในเบื้องต้นเรามาทำความรู้จักกับส่วนหลักของ Realm กันก่อน :)
Models
จะอยู่ในรูปแบบเหมือนกับ JavaBeans โดยต้อง extend RealmObject และใช้ annotation ใช้การจัดการข้อมูล ดังตัวอย่าง
public class User extends RealmObject {
@PrimaryKey
private String name;
private int age;
@Ignore
private int sessionId;
// getters & setters
}
** ข้อควรระวัง!! **จากโค้ดข้างบนจะเห็นว่ามีการใช้ annotation เพื่อจัดการข้อมูล ลองมาดูกันดีกว่าว่าแต่ละ annotation ใช้ทำอะไรบ้าง
getters และ setters จะถูก override ซึ่งหากมีการเขียน logic ต่างๆ ไว้ภายใน ก็จะไม่มีการถูกเรียกใช้งานอยู่ดีนะครับ
@Required
ใช้กับ field ที่ห้ามเป็น null
@PrimaryKey
กำหนดให้ field นั้นเป็น primary key ซึ่งจะสามารถมีได้เพียง 1 field เท่านั้น (รองรับ field types ดังต่อไปนี้ String, short, int และ long) และหากกำหนดให้เป็น primary key แล้วจะถือเป็นการกำหนดให้ field นั้น @Required ไปในตัว
@Index
เป็นการบอกว่าให้เพิ่ม search index ให้ field นั้นๆ ซึ่งจะทำให้การ insert ช้าลงและข้อมูลมีขนาดใหญ่ขึ้น แต่ query ได้เร็วขึ้น (รองรับ field types ดังต่อไปนี้ String, byte, short, int, long, boolean และ Date)
@Ignore
ใช้กับ field ที่ไม่ต้องการให้เก็บข้อมูลลง database
หากมองดูดีๆ จริงๆ แล้วก็เหมือนกับวิชา Database ที่เรียนมาเลยเนาะ และเรื่องสำคัญอีกอย่างที่จะต้องพูดถึง คือ
Field types
โดย field type ที่รองรับได้แก่ boolean, byte, short, ìnt, long, float, double, String, Date และ byte[] แต่สำหรับ integer type ทั้งหลาย (byte, short, int, long) ไม่ว่าจะใช้ type ไหน Realm จะ map ไว้เป็น long ทั้งหมด
นอกจากนี้ยังสามารถทำ model relations ได้ด้วย RealmObject และ RealmList<RealmObject>
การเขียนข้อมูล (Writes)
การเขียนข้อมูลทำได้ทั้งแบบ synchronous และ asynchronous แต่ว่าหากเขียน synchronous จะเกิดการ blocking UI thread เพื่อรอให้เขียนเสร็จ ซึ่งในแง่ของ UX แล้วคงจะไม่ดีซักเท่าไรที่แอพจะนิ่งไปดื้อๆ เพราะฉะนั้นในส่วนการเขียนจะขอพูดถึงแต่การเขียนแบบ asynchronous นะครับ (อู้นั่นเอง :P)
ก่อนจะเขียนข้อมูล มาดูวิธีการสร้าง Object กันก่อน
การสร้าง object ทำได้ 2 วิธี คือ
1. ให้ Realm สร้างขึ้นมาโดยตรงจาก class ที่ extends RealmObject
User user = realm.createObject(User.class);
user.setName("John");
user.setEmail("john@corporation.com");
2. ใช้คำสั่ง copyToRealm() หลังจากที่ new instance object ขึ้นมา (class นั้นต้อง extends RealmObject)User user = new User("John");
user.setEmail("john@corporation.com");
User realmUser = realm.copyToRealm(user);
การเขียนข้อมูลแบบ asynchronous
// สร้าง Realm object ขึ้นมาก่อน
Realm realm = Realm.getInstance(this);
// เรียกการใช้งานแบบ asynchronous
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
User user = realm.createObject(User.class);
user.setName("John");
user.setEmail("john@corporation.com");
}
});
การเขียนข้อมูลแบบ asynchronous จะทำงานอยู่บน background thread ซึ่งจะไม่เกิดการ blocking UI thread โดยเราจะสามารถรู้ได้ว่ามันทำงานเสร็จแล้วหรือยังได้ด้วย Realm.Transaction.Callback
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm bgRealm) {
...
}
}, new Realm.Transaction.Callback() {
@Override
public void onSuccess() {
// เขียนข้อมูลสำเร็จ
}
@Override
public void onError(Exception e) {
// หากเกิดความผิดพลาด transaction จะถูก roll back อัตโนมัติ
}
});
นอกจากนี้ asynchronous transaction สามารถอยู่ในรูปแบบ RealmAsyncTask object เพื่อใช้สำหรับ ยกเลิก transaction เมื่อออกจาก Activity/Fragment ก่อนที่ transaction จะทำงานสำเร็จ ซึ่งจะช่วยแก้ปัญหา app crash เมื่อ callback มีการอับเดต UI นั่นเอง
private RealmAsyncTask transaction;
public void saveUser() {
transaction = realm.executeTransaction(new Realm.Transaction() { ... });
}
public void onStop() {
if (transaction != null && !transaction.isCancelled()) {
transaction.cancel();
}
}
การอ่านข้อมูล (Queries)
Realm’s query อยู่ในรูปแบบของ Fluent interface ซึ่งจะทำให้ง่ายต่อการ query หลายๆ เงื่อนไข ถ้านึกภาพไม่ออกลองมาดูโค้ดกันเลยดีกว่า
RealmResults<User> result = realm.where(User.class)
.equalTo("name", "John")
.or()
.equalTo("name", "Peter")
.findAll();
** ข้อควรระวัง!! **
เมื่อไม่พบข้อมูลใดๆ RealmResults จะไม่เป็น null แต่จะมี size() เป็น 0 แทน
เงื่อนไข
สำหรับเงื่อนไขที่รองรับในการ query ก็จะมีดังต่อไปนี้- between, greaterThan(), lessThan(), greaterThanOrEqualTo() และ lessThanOrEqualTo()
- equalTo() และ notEqualTo()
- contains(), beginsWith() และ endsWith()
โดยมีข้อจำกัด คือ เงื่อนไขทั้งหมดไม่ได้รองรับทุก type สามารถดูรายละเอียดเพิ่มเติมที่ RealmQuery
Iterations
สามารถ loop เพื่อเข้าถึงแต่ละ object ใน RealmResults ได้ด้วยความสามารถของ Iterationsfor (User u : result) {
...
}
หรือจะใช้ for loop ปกติก็ยังได้
for (int i = 0; i < result.size(); i++) {
User u = result.get(i);
...
}
การเรียงลำดับ (Sorting)
โดย default จะเป็นการเรียงแบบ Ascending และสามารถสั่งเรียงแบบ Descending ได้โดยส่ง Sort.DESCENDING เข้าไปRealmResults<User> result = realm.where(User.class).findAll();
result.sort("age"); // Sort ascending
result.sort("age", Sort.DESCENDING);
การลบข้อมูล (Deletion)
// remove single match
result.remove(0);
result.removeLast();
// remove a single object
Dog dog = result.get(5);
dog.removeFromRealm();
// Delete all matches
result.clear();
Asynchronous Queries
โดยปกติการ Synchronous queries (แบบด้านบน) นั้นมีความเร็วมากพอที่จะไม่กระทบกับ UI thread แต่ในบางกรณี เช่น ข้อมูลที่ query ซับซ้อน หรือข้อมูลมีปริมาณเยอะๆ เราก็สามารถหลีกเลี่ยงการที่จะไป blocking UI thread ได้ ด้วยการใช้ Asynchronous queries แทน
RealmResults<User> result = realm.where(User.class)
.equalTo("name", "John")
.or()
.equalTo("name", "Peter")
.findAllAsync();
ด้วยความที่เป็น Asynchronous หากอยากรู้ว่า query ทำงานสำเร็จแล้วหรือยัง ต้อง add RealmChangeListener เข้าไป ซึ่งจะเข้า method onChange() เมื่อ query สำเร็จ และทุกๆ ครั้งที่ RealmResults มีการ update
result.addChangeListener(new RealmChangeListener() {
@Override
public void onChange() {
// จะเข้า method เมื่อ query สำเร็จ และทุกๆ ครั้งที่ RealmResults มีการ update
}
});
และเพื่อหลีกเลี่ยงเรื่อง memory leaks ควรทำการ unregister listeners ทุกครั้งที่ออกจาก Activity/Fragment
public void onStop () {
result.removeChangeListener(callback); // remove a particular listener
// or
result.removeChangeListeners(); // remove all registered listeners
}
นอกจากนี้ ยังสามารถเช็คว่า query ทำงานสำเร็จแล้วหรือยัง ด้วยการใช้ method isLoaded() ได้อีกด้วย
if (result.isLoaded()) {
// ข้อมูลพร้อมใช้งานแล้ว
}
Best Practice for Android
เนื่องจาก RealmObjects และ RealmResults ถูกออกแบบให้สามารถเข้าถึงได้ตลอดเวลา และเพื่อหลีกหลีก overhead ที่จะเกิดขึ้นในตอน เปิด/ปิด connenction จึงแนะนำสร้าง Realm instance ผ่านการเรียก Realm.getDefaultInstance() ลองมาดูโค้ดกันซักเล็กน้อยApplication
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// setup RealmConfiguration
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this).build();
Realm.setDefaultConfiguration(realmConfiguration);
}
}
Activity
public class MyActivity extends Activity {
private Realm realm;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
}
@Override
protected void onDestroy() {
super.onDestroy();
realm.close();
}
}
Fragment
ใช้ onStart()/onStop() สำหรับ Fragments เนื่องจาก onDestroy() อาจจะไม่ถูก callpublic class MyFragment extends Fragment {
private Realm realm;
@Override
public void onStart() {
super.onStart();
realm = Realm.getDefaultInstance();
}
@Override
public void onStop() {
super.onStop();
realm.close();
}
}
Reuse RealmResults and RealmObjects
เมื่อมีการเปลี่ยนแปลงข้อมูล RealmObjects และ RealmResults จะถูก refresh อัตโนมัติ เพราะฉะนั้นหากต้องการให้ UI เปลี่ยนตามข้อมูลที่เปลี่ยนไป ก็สามารถเปลี่ยน UI ได้โดยผ่าน RealmChangedListener นั่นเองpublic class MyActivity extends Activity {
private Realm realm;
private RealmResults allPersons;
private RealmChangeListener realmListener = new RealmChangeListener() {
@Override
public void onChange() {
// Just redraw the views. `allPersons` already contain the
// latest data.
invalidateView();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
realm.addRealmChangeListener(listener);
allPerson = realm.where(Person.class).findAll(); // Create the "live" query result
setupViews(); // Initial setup of views
invalidateView(); // Redraw views with data
}
// ...
}
ถ้าหากเปรียบบทความนี้กับเรื่องความรักของหนุ่มสาวแล้ว บทความนี้ก็เป็นเพียงแค่แม่สื่อ ที่จะทำให้หนุ่มสาวได้รู้จักกัน ซึ่งหลังจากนี้จะสานสัมพันธ์กันต่อหรือไม่ ก็เป็นเรื่องของคน 2 คนแล้วหละครับ :3
นอกจากเรื่องที่ได้เล่าไปในบทความ ยังมีเรื่องที่น่าสนใจอีก ไม่ว่าจะเป็น Relationships Migrations และอื่นๆ อีกมากมาย แต่ด้วยพลังวัฒรที่ยังไม่กล้าแกร่งพอ จึงขอจบบทความไว้ด้วยเนื้อหาเพียงเท่านี้ก่อน
หากสนใจขอแนะนำให้ไปอ่าน document ในเว็บ https://realm.io ได้เลยนะครับ เขียนไว้ดีมากๆ (แนะนำควรอ่านอย่างยิ่ง ถ้าไปอ่านแล้วก็รบกวนเขียนต่อด้วยนะ ถ้าไม่เขียนต่อภายใน 3 วัน 7 วัน ท่านจะต้อง .... น่าจะผิดเรื่องแล้วหละ) อีกส่วนหนึ่งที่น่าประทับใจของ library ตัวนี้ คือมี example ให้ดูเพียบเลย
สุดท้ายนี้หากมีข้อผิดพลาดประการใดก็ขออภัยไว้ ณ ที่นี้ด้วยครับ
ปล. ท่านไหนเคยใช้งานจริงๆ ติดปัญหา หรือ มีคำแนะนำอะไร ฝากไว้ในช่อง comment ได้เลยนะคร้าบ
ลิงค์อ้างอิง
Official Realm website : https://realm.io/Realm repository : https://github.com/realm/realm-java
Slideshare(Christian Melchior) : Realm: Building a mobile database