Lỗi khi thay đổi trong Realm - Android

Giới thiệu

Như các bạn đã biết thị hiện nay Realm được sử dụng khá rộng rãi do những ưu điểm vượt bậc so với các hệ quản trị CSDL khác.

Tuy nhiên Realm không phải là toàn năng và chắc chắn nó vẫn có nhiều lỗi mà nếu người dùng không cẩn thận sẽ mắc phải.

Một trong số đó là lỗi khi bạn thay đổi trong Database. (Thêm sửa xóa 1 trường hay 1 bảng bất kỳ). Lỗi này khiến cho Realm họat động không còn chính xác nữa và nó thậm chí còn khiến cho app của bạn có thể bị crash.

    time: FATAL EXCEPTION: main
    Process: com.example.framgia.imarketandroid, PID: 18100
    io.realm.exceptions.RealmMigrationNeededException: RealmMigration must be provided
    at io.realm.BaseRealm.migrateRealm(BaseRealm.java:717)
    at io.realm.Realm.migrateRealm(Realm.java:1351)
    at io.realm.Realm.migrateRealm(Realm.java:1338)
    at io.realm.Realm.createInstance(Realm.java:221)
    at io.realm.RealmCache.createRealmOrGetFromCache(RealmCache.java:126)
    at io.realm.Realm.getInstance(Realm.java:178)

Và ngày hôm nay mình sẽ chia sẻ về cách khắc phục nhược điểm này của Realm

Nguyên nhân

Nếu bạn nào hay sử dụng SQLite thì chắc chắn đã quen với việc phải tăng version database mỗi khi bạn có thay đổi trong database đúng không nào. Thì đối với Realm cung vậy. Lỗi này nguyên nhận chính là do bạn đã thay đổi database mà chưa cập nhập version cho nó.

Khắc phục

Realm hỗ trợ chúng ta 3 phương pháp một khi bạn cập nhập database. Rất hưu ích và vô cùng đơn giản. Mình sẽ trình bày cả 3 cách và các bạn có thể chọn cách nào phù hợp với dự ạn của các bạn cũng được.

Cách 1: Xác định rõ các phiên bản khi tạo 1 RealmConfiguration

        // If the schema does not have that version a RealmMigrationNeededException will be thrown.
        RealmConfiguration config0 = new RealmConfiguration.Builder()
                .name("default0.realm")
                .schemaVersion(3)
                .build();

        // Bạn có thể gọi  Realm.migrateRealm(). và đặt trong try/catch để bắt trường hợp ngoại lệ
        try {
            Realm.migrateRealm(config0, new Migration());
        } catch (FileNotFoundException ignored) {
            // If the Realm file doesn't exist, just ignore.
        }
        realm = Realm.getInstance(config0);
        showStatus("Default0");
        showStatus(realm);
        realm.close();

Cách 2: Bạn có thể sử dụng phương thức .deleteRealmIfMigrationNeeded() nếu bạn không quan tâm đến migrations. Tuy nhiên điều này sẽ xóa tất cả dữ liệu trong Realm.

    RealmConfiguration config2 = new RealmConfiguration.Builder()
                .name("default2.realm")
                .schemaVersion(3)
                .deleteRealmIfMigrationNeeded()
                .build();

**Cách 3 **: Bạn sử dụng tính năng mà Realm đã hỗ trợ rất mạnh .

Khi sử dụng Realm chúng ta phải kế thừa lớp RealmMigration để thuận tiện cho việc cập nhật CSDL, thêm/bớt các bảng cũng như các trường.

Giả sử ta có 1 lớp Migration extend RealmMigration như sau :

    public class Migration implements RealmMigration {
        // TODO something
    }

Các trường hợp thay đổi trong Realm mà cần phải cập nhập trong Migration

  1. Thay đổi các trường trong 1 bảng

Ví dụ :

Old_Version :

    class Person
                @Required
                String firstName;
                @Required
                String lastName;
                int    age;

New_Version:

     class Person
                @Required
                String fullName;            // combine firstName and lastName into single field.
                int age;

Vậy bạn cần viết đoạn mã sau để cập nhật version cho nó . Giả sử lúc này version lúc này là 0.

    if (oldVersion == 0) {
            RealmObjectSchema personSchema = schema.get("Person");

            // Combine 'firstName' and 'lastName' in a new field called 'fullName'
            personSchema
                    .addField("fullName", String.class, FieldAttribute.REQUIRED)
                    .transform(new RealmObjectSchema.Function() {
                        @Override
                        public void apply(DynamicRealmObject obj) {
                            obj.set("fullName", obj.getString("firstName") + " " + obj.getString("lastName"));
                        }
                    })
                    .removeField("firstName")
                    .removeField("lastName");
            oldVersion++;
        }

Trước tiên bạn phải addField mới, sau đó thì removeField cũ đi. Nhớ tăng version .

  1. Add a new model class
        class Pet                   // add a new model class
                    @Required
                    String name;
                    @Required
                    String type;

          >>>>>>>>>>>>>>>>>>>>>

                class Person
                    @Required
                    String fullName;
                    int age;
                    RealmList<Pet> pets; // add an array property

Giả sử đây là lần thay đổi thứ 2 thì old_version sẽ là 1.

    if (oldVersion == 1) {

            // Create a new class
            RealmObjectSchema petSchema = schema.create("Pet")
                    .addField("name", String.class, FieldAttribute.REQUIRED)
                    .addField("type", String.class, FieldAttribute.REQUIRED);

            // Add a new field to an old class and populate it with initial data
            schema.get("Person")
                .addRealmListField("pets", petSchema)
                .transform(new RealmObjectSchema.Function() {
                    @Override
                    public void apply(DynamicRealmObject obj) {
                        if (obj.getString("fullName").equals("JP McDonald")) {
                            DynamicRealmObject pet = realm.createObject("Pet");
                            pet.setString("name", "Jimbo");
                            pet.setString("type", "dog");
                            obj.getList("pets").add(pet);
                        }
                    }
                });
            oldVersion++;
        }
  1. Trường hợp cuối cùng
             class Pet
                    @Required
                    String name;
                    int type;               // type becomes int

      >>>>>>>>>>>>>>>>>>>>>

                class Person
                    String fullName;        // fullName is nullable now
                    RealmList<Pet> pets;    // age and pets re-ordered (no action needed)
                    int age;

Tiếp tục tưởng tượng thì bây giờ old_version sẽ là 2

     if (oldVersion == 2) {
            RealmObjectSchema personSchema = schema.get("Person");
            personSchema.setNullable("fullName", true); // fullName is nullable now.

            // Change type from String to int
            schema.get("Pet")
                .addField("type_tmp", int.class)
                .transform(new RealmObjectSchema.Function() {
                    @Override
                    public void apply(DynamicRealmObject obj) {
                        String oldType = obj.getString("type");
                        if (oldType.equals("dog")) {
                            obj.setLong("type_tmp", 1);
                        } else if (oldType.equals("cat")) {
                            obj.setInt("type_tmp", 2);
                        } else if (oldType.equals("hamster")) {
                            obj.setInt("type_tmp", 3);
                        }
                    }
                })
                .removeField("type")
                .renameField("type_tmp", "type");
            oldVersion++;
        }

Ngoài ra các bạn có thể tham khảo chi tiết về Realm tại đây

Chúc các bạn một ngày làm việc vui vẻ !