1. Context

Database ở các mobile app có một đặc trưng khá khác biệt so với các ứng dụng Web là bị hạn chế bởi tài nguyên có hạn, do đó nó đòi hỏi một tốc độ query hay update dữ liệu cực kỳ nhanh mà không làm ảnh hưởng đến performance của ứng dụng. Các ứng dụng trước đây thường ít sử dụng database ở local mà chủ yếu load từ API, nếu có cũng ở mức giới hạn rất nhỏ và thường là để lưu trữ vài thông tin cơ bản của ứng dụng, người dùng ... Trong những xu hướng làm ứng dụng hiện đại hơn, đặc biệt là với những ứng dụng đồng hộ hóa giữa client và server, vai trò của database ngày càng được nâng cao. Điều đó đồng nghĩa với lượng data lưu trữ ở ứng dụng ngày càng lớn và đòi hỏi một tốc độ xử lý nhanh mà không ảnh hưởng đến performance của ứng dụng. Realm - No-SQL database - ra đời để thay thế hàng loạt các database cũ hơn, hỗ trợ multi platform và đặc biệt có tốc độ tuyệt vời. Bài viết hôm nay, tôi sẽ đi qua một số khái niệm chính trong Realm Android và việc combine nó với Reactive Programing Framework là RxAndroid.

2. Why Realm?

a. Simple

  • Realm được thiết kế với tiêu chí là ĐƠN GIẢN - DỄ SỬ DỤNG.
  • Realm expose data trực tiếp dưới dạng Object - Loại bỏ các vấn đề khi làm việc với ORM mặc dù cơ bản nó vẫn tuân thủ ORM.

Define một RealmObject trong Realm - tương đương với một Table trong database

public class Dog extends RealmObject {
    private String name;
    private int age;

    // ... Generated getters and setters ...
}

b. Fast

  • Realm là một no-SQL database - đặc trưng là tốc độ query - insert - update cực kỳ nhanh chóng.

Combine với một số Database thông dụng hiện tại.

  • Count

  • Query

  • Insert

c. Multi Platform

Realm support khá nhiều các platform và các ngôn ngữ khác nhau: Object C - Swift - Java (Android) - JavaScript - Xamarin.

d. Fully support

  • Realm đang trong giai đoạn phát triển và hoàn thiện với rất nhiều tính năng mới hỗ trợ cho developer.
  • Realm được support rất tốt từ một cộng đồng khá lớn và các contributor nhiệt tình.

3. Work with Realm

a. Initialize Realm

Realm.init(context);

b. Get a Realm instance

Realm realm = Realm.getDefaultInstance();

  • Realm.getDefaultInstance() sẽ tạo ra một Realm instance tương ứng với current thread. Do đó, trong một ứng dụng, bạn có thể có nhiều Realm instance cùng lúc.
  • RealmObject không thể work cross thread - nghĩa là bạn không thể sử dụng RealmObject được query từ một thread này trong một thread khác.

c. Create a RealmObject

// Define your model class by extending RealmObject
public class Dog extends RealmObject {
    private String name;
    private int age;

    // ... Generated getters and setters ...
}
public class Person extends RealmObject {
    @PrimaryKey
    private long id;
    private String name;
    private RealmList<Dog> dogs; // Declare one-to-many relationships

    // ... Generated getters and setters ...
}

d. Query

final RealmResults<Dog> puppies = realm.where(Dog.class).lessThan("age", 2).findAll();
puppies.size();

e. Insert to Realm Database

  • Synchronous
realm.beginTransaction();
final Dog managedDog = realm.copyToRealm(dog); // Persist unmanaged objects
Person person = realm.createObject(Person.class); // Create managed objects directly
person.getDogs().add(managedDog);
realm.commitTransaction();
  • Asynchronous
// Asynchronously update objects on a background thread
realm.executeTransactionAsync(new Realm.Transaction() {
    @Override
    public void execute(Realm bgRealm) {
        Dog dog = bgRealm.where(Dog.class).equalTo("age", 1).findFirst();
        dog.setAge(3);
    }
}, new Realm.Transaction.OnSuccess() {
    @Override
    public void onSuccess() {
        // Original queries and Realm objects are automatically updated.
        puppies.size(); // => 0 because there are no more puppies younger than 2 years old
        managedDog.getAge();   // => 3 the dogs age is updated
    }
});

f. Listen Data Changed

puppies.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<Dog>>() {
    @Override
    public void onChange(RealmResults<Dog> results, OrderedCollectionChangeSet changeSet) {
        // Query results are updated in real time with fine grained notifications.
        changeSet.getInsertions(); // => [0] is added.
    }
});

4. Combine with RxAndroid

Việc combine Realm vs RxAndroid sẽ mang lại khá nhiều lợi ích trong việc làm code trở nên clean hơn, và có thể mix giữa remote API và Local Database trong các ứng dụng đòi hỏi sự đồng bộ. Một ví dụ cụ thể là khi bạn sử dụng RxAndroid combine vs Retrofit để tạo một API service, khi work với remote API và nhận được response, ta muốn mix giữa response này với việc save lại data vào local database, muốn mix các stream này lại thành một để handle result được hiệu quả, việc này dẫn đến bạn phải combine giữa RxAndroidRealm để tạo thành một stream work với local database. Đến với một implementation cụ thể của việc combine này.

  • Tạo ra một API để execute một async transaction.
public <T> Observable<T> realmTransactionAsync(
            final Action2<Subscriber<? super T>, Realm> action) {
        return Observable.create(new Observable.OnSubscribe<T>() {
            @Override
            public void call(final Subscriber<? super T> subscriber) {
                action(new Action1<Realm>() {
                    @Override
                    public void call(final Realm realm) {
                        realm.executeTransactionAsync(new Realm.Transaction() {
                            @Override
                            public void execute(Realm realm) {
                                action.call(subscriber, realm);
                            }
                        }, new Realm.Transaction.OnSuccess() {
                            @Override
                            public void onSuccess() {
                                subscriber.onCompleted();
                            }
                        }, new Realm.Transaction.OnError() {
                            @Override
                            public void onError(Throwable error) {
                                subscriber.onError(error);
                            }
                        });
                    }
                });
            }
        }).subscribeOn(AndroidSchedulers.mainThread());
    }
  • Insert data
realmTransactionAsync(new Action2<Subscriber<? super Boolean>, Realm>() {
            @Override
            public void call(Subscriber<? super Boolean> subscriber, Realm realm) {
                Dog dog = new Dog();
                dog.setName("Rex");
                dog.setAge(1);
                realm.copyToRealm(dog);
                subscriber.onNext(true);
            }
        });
  • Combine with remote API
getDog(1).flatMap(new Func1<Dog, Observable<Boolean>>() {
            @Override
            public Observable<Boolean> call(final Dog dog) {
                return realmTransactionAsync(new Action2<Subscriber<? super Boolean>, Realm>() {
                    @Override
                    public void call(Subscriber<? super Boolean> subscriber, Realm realm) {
                        realm.copyToRealm(dog);
                        subscriber.onNext(true);
                    }
                });
            }
        });

5. Conclusion

Realm với những điểm ưu việt đang trở thành một mobile platform database mới và dần thay thế các database truyền thống. Tuy nhiên, nó cũng tồn tại nhiều điểm chưa tốt và đang dần được cải thiện. Hy vọng qua bài viết này, tôi sẽ giúp các bạn có cái nhìn tổng quan về Realm Database và có thể apply vào dự án của mình.