12/08/2018, 14:53

Realm với Dagger2

Những công nghệ Android đáng chú ý hiện tại là: Realm, Dagger và Unit Testing. Do đó, nên tìm kiếm cơ hội để cải tiến mã code bằng một cách nào đó kết hợp chúng. Và vấn đề migration trong Realm có thể được cải thiện đáng kể bằng cách sử dụng Dagger 2. Chúng ta sẽ tiến hành refactor class ...

Những công nghệ Android đáng chú ý hiện tại là: Realm, Dagger và Unit Testing. Do đó, nên tìm kiếm cơ hội để cải tiến mã code bằng một cách nào đó kết hợp chúng. Và vấn đề migration trong Realm có thể được cải thiện đáng kể bằng cách sử dụng Dagger 2.

Chúng ta sẽ tiến hành refactor class Migration:

public class Migration implements RealmMigration {
   @Override
   public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
     RealmSchema schema = realm.getSchema();

     if (oldVersion == 1) {
         RealmObjectSchema recipeSchema = schema.get( "Recipe" );
         recipeSchema.addField( RecipeFields.NUMBER_OF_STARS, Integer.class)
                                    .transform( new RealmObjectSchema.Function()
                                    {
                                            @Override
                                            public void apply (DynamicRealmObject obj)
                                            {
                                                   obj.setInt( RecipeFields.NUMBER_OF_STARS, 5);
                                            }
                                    });
                                    
                                    oldVersion++;
     }

     if (oldVersion == 2)  {
         RealmObjectSchema recipeSchema = schema.get( "Recipe" );
         recipeSchema.addField( RecipeFields.IS_YUMMY, boolean.class);
     }
     
 }

Với chỉ có hai phiên bản cập nhật, chúng ta đã có một phương thức với kích thước hợp lý để giải quyết. Hãy bắt đầu bằng cách tạo ra các test migration, với Dagger's Multibinding.

Điều đầu tiên cần làm là tạo một Interface, VersionMigration:

interface VersionMigration
{
    void migrate (final DynamicRealm realm, long oldVersion);
}

Phương thức migrate lấy khi một DynamicRealm instance và một khoảng thời gian dài đại diện cho phiên bản trước đó của schema mà bạn muốn migrate.

Với tính khả dụng này, chúng ta có thể tạo ra hai lớp VersionMigration implement interface trên. Đây là việc implement cho class Version1Migration:

class Version1Migration implements VersionMigration
{
    private static final String RECIPE = "Recipe";

    /************************************************
     // Version 2
     class Recipe
     Integer numberOfStars //added
     ************************************************/
    @Override
    public void migrate (DynamicRealm realm, long oldVersion)
    {
        if ( oldVersion == 1 )
        {
            RealmObjectSchema recipeSchema = getObjectSchema( realm );
            recipeSchema.addField( RecipeFields.NUMBER_OF_STARS, Integer.class )
                        .transform( new RealmObjectSchema.Function()
                        {
                            @Override
                            public void apply (DynamicRealmObject obj)
                            {
                                obj.setInt( RecipeFields.NUMBER_OF_STARS, 5 );
                            }
                        } );

            Timber.d( "migration complete" );
        }
    }

    RealmObjectSchema getObjectSchema (DynamicRealm realm)
    {
        RealmSchema schema = realm.getSchema();
        return schema.get( RECIPE );
    }
}

Trên dòng 12, chúng ta implement phương thức migrate. Lưu ý rằng chúng ta đã chỉ được dán trong cùng mã mà chúng ta đã có trong lớp Migration ban đầu. Sự khác biệt chính là trên dòng 16, nơi mà chúng ta sử dụng phương thức getObjectSchema để lấy schema thay vì lấy nó trực tiếp. Tôi sẽ giải thích tại sao chúng tôi đã làm theo cách này trong giây lát.

Tiếp theo, chúng ta sẽ tạo một mô-đun Dagger mới, tên MigrationsModule. Sau đó bằng các chú thích sau: @Provides, @IntoMap, và @IntKey Tôi định nghĩa làm thế nào để tạo và inject VersionMigrations. Sự kết hợp của các annotation này cho phép tất cả VersionMigration Provider được đưa vào Map. Chìa khóa của map sẽ là một Số nguyên (Integer) tương ứng với phiên bản schema trước đó mà nên sử dụng cho VersionMigration.

@Module
public class MigrationsModule
{
    @Provides
    @IntoMap
    @IntKey( 1 )
    static VersionMigration provideVersion1Migration ()
    {
        return new Version1Migration();
    }

    @Provides
    @IntoMap
    @IntKey( 2 )
    static VersionMigration provideVersion2Migration ()
    {
        return new Version2Migration();
    }
}

Ví dụ: nếu phiên bản schema trước là 2 và phiên bản schema hiện tại là 3, thì ta sẽ sử dụng class Version2Migration. Ta sẽ không cần sử dụng bất kỳ phiên bản VersionMigration nào khác. Tuy nhiên, nếu phiên bản schema trước là 1 và phiên bản schema hiện tại là 3, thì ta sẽ sử dụng cả lớp Version1Migration và Version2Migration. Tất cả sẽ kết hợp lại khi chúng ta xem xét lớp Migration đã được cập nhật.

@Reusable
public class Migration implements RealmMigration
{
    private Map<Integer, Provider<VersionMigration>> versionMigrations;

    @Inject
    Migration (Map<Integer, Provider<VersionMigration>> versionMigrations)
    {
        this.versionMigrations = versionMigrations;
    }

    @Override
    public void migrate (final DynamicRealm realm, long oldVersion, long newVersion)
    {
        for ( int i = ( int ) oldVersion; i < newVersion; i++ )
        {
            final Provider<VersionMigration> provider = versionMigrations.get( i );
            if ( provider != null )
            {
                VersionMigration versionMigration = provider.get();
                versionMigration.migrate( realm, i );
            }
        }
    }

}

Class đã được "Daggerized"! Chúng ta bắt đầu bằng cách đưa Map của VersionMigration Provider vào constructor dòng 7. Nhớ lại rằng trong MigrationsModule chúng ta đã sử dụng ba chú thích: @Provides, @IntoMap, và @IntKey. Dựa trên đó, Dagger đủ thông minh để thu thập cả hai provider với nhau và lưu giữ chúng trong Map sử dụng hằng số nguyên mà đã được xác định là key.

Di chuyển xuống phương thức migrate. Ở dòng 15, chúng ta có một vòng lặp đơn giản bắt đầu với oldVersion và đi cho đến khi chúng ta đến được phiên bản mới. Lưu ý rằng phiên bản cũ tương ứng với phiên bản schema đang hoạt động trên thiết bị của người dùng. Đây là phiên bản mà chúng ta muốn migrate, để chúng ta có thể vào phiên bản mới. Sự khiếp sợ chính xảy ra ở dòng 17 - 22. Chúng ta tìm trong versionsMigrations Map để biết provider sử dụng đúng giá trị biến vòng lặp. Sau đó, chúng ta trả lại một thể hiện của VersionMigration thích hợp và thực hiện phương thức migrate. Điều này có nghĩa là cho dù chúng ta cần migrate bao nhiêu lần trong tương lai, chúng ta cũng không phải lo lắng về class này nữa.

Hơn nữa, chúng ta cũng có logic chuyển đổi cô lập thành các bit testable. Dưới đây là một số unit test cho lớp Version1Migration sử dụng JUnit và Mockito.

public class Version1MigrationTest
{
    @Rule
    public MockitoRule rule = MockitoJUnit.rule();

    @Mock
    DynamicRealm dynamicRealm;

    @Mock
    RealmObjectSchema realmObjectSchema;

    private Version1Migration migration;

    @Before
    public void setUp ()
    {
        migration = new Version1Migration()
        {
            @Override
            RealmObjectSchema getObjectSchema (DynamicRealm realm)
            {
                return realmObjectSchema;
            }
        };
    }

    @Test
    public void shouldNotBeNull ()
    {
        assertNotNull( migration );
    }

    @Test
    public void migrate_shouldDoNothing_whenNotProperOldVersion ()
    {
        migration.migrate( dynamicRealm, 2 );

        verifyZeroInteractions( dynamicRealm );
    }

    @Test
    public void migrate_shouldAddField ()
    {
        when( realmObjectSchema.addField( anyString(), eq( Integer.class ) ) ).thenReturn( realmObjectSchema );

        migration.migrate( dynamicRealm, 1 );

        verify( realmObjectSchema ).addField( RecipeFields.NUMBER_OF_STARS, Integer.class );
        verify( realmObjectSchema ).transform( any( RealmObjectSchema.Function.class ) );
    }

}

Nguồn: http://www.adavis.info/2017/03/realm-migrations-supercharged-with.html

0