17
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AndroidAnnotations を使って Realm をもっと使いやすくするパターンの紹介

Last updated at Posted at 2015-12-23

Realm アドベントカレンダーが空いてるので参加します。

Android のコポーネントで Realm をどう管理するのか問題

Realmは Closeable なクラスで、利用をし終わったら close をしなければなりません。 Realm を利用し終わるとは以下のパターンがあるのでは無いでしょうか。

  1. Realm への書き込みが終わる
  2. Realm から読み込んだデータへの参照が終わる

「Realm への書き込みが終わる」は特に意識をする必要は無いでしょう。ちょっと他のDBと比べて特殊なのが2番めの「Realm から読み込んだデータへの参照が終わる」です。

これは言い換えると「RealmQuery の結果を利用する間は、 Realm を close してはならない」と言うことです。そのため、例えば以下のコードを実行するとアプリがクラッシュをします。

// MainActivity.java

public class MainActivity extends Activity {

  TextView textView;

  @Override
  protected void onCreate(Bundle savedInstance) {
    // 省略
    Realm realm = Realm.getInstance(this);
    MyRealmObject realmObject; // MyRealmObject extends RealmObjecrt
    realmObject = realm.query(/** your query */).findFirst();
    realm.close(); // No!!!
    textView.setText(realmObject.getText()); // This Realm instance has already been closed, making it unusable.
  } 
}

そこで例えば次のように Realm をフィールド変数として利用するパターンが考えられます。

// MainActivity.java

  Realm realm;
  TextView textView;

  @Override
  protected void onCreate(Bundle savedInstance) {
    realm = Realm.getInstance(this);
    realmObject = realm.query(/** your query */).findFirst();
    textView.setText(realmObject.getText()); 
  }

    @Override
    protected void onDestroy() {
        realm.close();
        super.onDestroy();
    }

大体の場合このパターンで問題無いと言えるでしょう。しかし、テストがし難い問題があります。フィールド変数で定義された Realm は確実に onCreate で初期化されるため、テストの時はテスト用のRealmを参照するとかと言った方法が取りにくくなります。

また、Realmの初期化の方法がコンポーネントに依存してしまうので FirstActivitySecondActivity で利用するRealmが同じである事を保証することができない、また利用するRealmを変更したい場合に変更箇所が多くなってしまいます。まとめると

  • Realm に依存する部分のユニットテストのコードが書きにくくなる
  • Realm がコンポーネントに依存するため、 同じ Realm の再利用や設定変更がやりにくくなる

と言ったところです。

それ、 AndroidAnnotations ならなんとかなるよ

この問題の解決をするのに、AndroidAnnotationsのDI機能を利用することでわりとなんとかなります。

AndroidAnnotations を利用することで、View などの依存性を Activity, Service, Fragment, BroadcastReceiver, ContentProvider へ注入することができますが、何を隠そうそれ以外のモデルについても依存性注入が可能になります。

この機能を利用して「RealmConfigurator を返すモデル」を定義することで、Realm のコンポーネントへの依存を解消できるようになります。それでは実際にコードを見てみましょう。

ちなみに、テストはRobolectricを利用してJVM上で実行することを想定しています。

// MainRealmConfigurator.java

@Bean
public class MainRealmConfigurator {

  private final RealmConfiguration realmConfiguration;

  public MainRealmCOnfigurator(Context context) {
    realmConfiguration = new RealmConfiguration.Builder(context.getCacheDir())
                .setModules(new Module())
                .build();
  }

  public RealmConfiguration getConfiguration() {
    return realmConfiguration;
  }
}
// RealmFactory.java
// PowerMock を利用するためにラッパーを用意しています。
public class RealmFactory {

  public static Realm getInstance(RealmConfiguration config) {
    return Realm.getInstance(config);
  }
}
// MainActivity.java
@EActivity
public class MainActivity extends Activity {
 
  @EBean
  MainRealmConfigurator mainRealmConfig;
  Realm realm;

  @AfterInject
  void initRealm() {
    realm = RealmFactory .getInstance(mainRealmConfig.getConfiguration());
  }

  @Override
  protected void onCreate(Bundle savedInstance) {
    super.onCreate(savedInstance);
    MyRealmObject realmObject = //省略
    textView.setText(realmObject.getText());
  }
  
  @Override
  protected void onDestroy() {
    realm.close();
    super.onDestroy();
  }
}

@Bean@EBean アノテーションを利用することで Realm の設定をコンポーネントから分離しその結果複数のコンポーネントで同じ設定の Realm を再利用できるようになりました。

ただし、今のところ AndroidAnnotations は「テストの時に別のインスタンスを利用する」機能をサポートしていません。この辺りの機能については、 PowerMock で補います。

PowerMock を Realm に依存しているプロジェクトで使う方法は、Realmに依存する部分もJVMでテストする話を参考にしてください。

この Activity に対してテストを行うときは


// MainActivityTest.java

@RunWith(CustomRoboRunner.class)
@Config(manifest = Config.NONE, sdk = Build.VERSION_CODES.LOLLIPOP, constants = BuildConfig.class)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
@PrepareForTest({Realm.class, RealmFactory.class,MainRealmConfigurator.class})
public class MainActivityTest {

    @Rule
    public PowerMockRule rule = new PowerMockRule();

    RealmConfiguration configuration;
    MainRealmConfigurator_ mockConfigurator;

    @Before
    public void mockRealm() {
        final Realm mock = mock(Realm.class);
        mockStatic(RealmFactory.class);
        when(RealmFactory.getInstance(Matchers.<RealmConfiguration>any())).thenReturn(mock);
    }

    @Before
    public void initConfig() {
        configuration = mock(RealmConfiguration.class);
        mockConfigurator = mock(MainRealmConfigurator_.class);
        when(mockConfigurator.getRealmConfiguration()).thenReturn(configuration);
        mockStatic(MainRealmConfigurator_.class);
        when(MainRealmConfigurator_.getInstance_(any())).thenReturn(mockConfigurator);
    }

    @Test
    public void injectionTest() {
        final ActivityController<MainActivity_> activityController = Robolectric.buildActivity(MainActivity_.class);
        activityController.create();
        final MainActivity activity = activityController.get();
        
        assertThat(activity.realmConfigurator, is(sameInstance(mockConfigurator)));
        assertThat(activity.realmConfigurator.getRealmConfiguration(), is(sameInstance(configuration)));
    }
}

今回は RealmConfiguration を確認しているだけですが、本当なら適当にモックをした RealmResult を用意してUIなんかの変更をテストする物でしょう。

まとめ

AndroidAnnotations を利用して Realm の初期化をコンポーネントに依存せず行う方法を紹介しました。 AndroidAnnotations の機能の限界もあって、一部は PowerMock で補っています。

また、 PowerMock で補っていますが、 Instrumentation Test の方だと PowerMock が利用できないので別の手段を考える必要があります。

17
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?