Posted at
RealmDay 5

Realm Android mini Tips

More than 3 years have passed since last update.


概要

Realm を Android で使うときの小さな Tips です。

すいません>< 別のネタを用意していたのですが、準備が間に合いませんでした!


Tips


ライフサイクルに揃える

Android で Realm を使う場合、必ず close をする必要があります。 Activity / Fragment の中で Realm を使う場合、 UI スレッドの上であれば、ライフサイクルに合わせて Realm オブジェクトを管理すると良いでしょう。

public class MainActivity extends AppCompatActivity {

private Realm mRealm; // 1. インスタンス変数として Realm オブジェクトを管理する

@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mRealm = Realm.getDefaultInstance(); // 2. onCreate で参照する
}

@Override protected void onDestroy() {
mRealm.close(); // 3. onDestroy で close する
super.onDestroy();
}
}

public class MainFragment extends Fragment {

private Realm mRealm; // 1. インスタンス変数として Realm オブジェクトを管理する

@Override public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
mRealm = Realm.getDefaultInstance(); // 2. onCreateView で参照する
return super.onCreateView(inflater, container, savedInstanceState);
}

@Override public void onDestroyView() {
mRealm.close(); // 3. onDestoryView で close する
super.onDestroyView();
}
}

この場合、 UI スレッドで Realm オブジェクトが管理されるので、別のスレッドからは mRealm を使うことはできません。別のスレッドから呼び出される処理がある場合は、別個 Realm オブジェクトを取得する必要があります。

new Thread(new Runnable() {

@Override public void run() {
Realm realm = Realm.getDefaultInstance();
try {
// realm を使って処理する
} finally {
if (realm != null) realm.close();
}
}
}).start();


try-finally で確実に close する

Android 4.4 以降をターゲットにできれば try-with-resources を使って、もう少しだけスマートに Realm オブジェクトの close まで面倒を見ることができますが、中々そうもできないのが現状……。代わりに try-finally で確実に close するようにしましょう。

Realm realm = Realm.getDefaultInstance();

try {
// realm を使って処理する
} finally {
if (realm != null) realm.close();
}

Android 4.4 以降のみをターゲットとしたアプリであれば、

try (Realm realm = Realm.getDefaultInstance()) {

// realm を使って処理する
}

記述量も減ってスマートですね (;´∀`)


Retrolambda を使って Lambda 式を持ち込む

Java 8 から Java で Lambda 式が使えるようになりました。でも Android Java は Java 6 と Java 7 が混ざった亜種みたいなもの……。 Android で Java 8 が使えるようになるのは来世なのではないかと思っていますが、 Retrolambda を組み合わせれば未来から Lambda 式を取り寄せることができます。

Retrolambda を使うには Java 8 の JDK がインストールされていて、パスが通っている必要があります。また、アプリケーション開発には Android Studio を使って開発していると思いますので、アプリケーションプロジェクトの build.gradle を開いて、 apply plugin: 'com.android.application' と書いているところを以下のように書き換えます。

buildscript {

repositories {
mavenCentral()
}

dependencies {
classpath 'me.tatarka:gradle-retrolambda:3.2.4'
}
}

repositories {
mavenCentral()
}

apply plugin: 'com.android.application'
apply plugin: 'me.tatarka.retrolambda'

次に、

android {

compileSdkVersion 23
buildToolsVersion "23.0.2"
// 以下省略

で始まるセクションを見つけて、以下のように書き換えます。

android {

compileSdkVersion 23
buildToolsVersion "23.0.2"

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// 以下省略

これで準備は完了です。少し極端な例ですが、 Retrolambda を使うと記述量が減って、見やすいコードになります。例えば Retrolambda を使わずに書くと、

new Thread(new Runnable() {

@Override
public void run() {
Realm threadRealm = Realm.getDefaultInstance();
try {
threadRealm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
Person person = realm.createObject(Person.class);
person.setId(1);
person.setName("Realm is Awesome");
}
});
} finally {
if (threadRealm != null) threadRealm.close();
}
}
}).start();

インデントが深く (この記事では 2 インデントにしていますが、 4 インデントだったらさらに深く見えるでしょう) 、冗長なコードに見えます。これを Retrolambda を組み合わせれば、

new Thread(() -> {

Realm threadRealm = Realm.getDefaultInstance();
try {
threadRealm.executeTransaction(realm -> {
Person person = realm.createObject(Person.class);
person.setId(1);
person.setName("Realm is Awesome");
});
} finally {
if (threadRealm != null) threadRealm.close();
}
}).start();

ここまですっきりさせることができます。 Realm の executeTransaction メソッドは非常によく使うメソッドなので、これをスッキリできるだけでも嬉しいですね。

また、 Realm Java 0.84 からは非同期クエリ、非同期トランザクションがサポートされました。非同期トランザクションは executeTransaction メソッドの第 2 引数に Realm.Transaction.Callback オブジェクトのインスタンスを渡して、トランザクションの成功・失敗を受け取ることができますが、このオブジェクトは onSuccessonError(Exception e) の 2 つのコールバックを実装するため、そのままでは Retrolambda を使ってスッキリと書くことができません。

mRealm.executeTransaction(realm -> {

// realm を使って処理する
}, new Realm.Transaction.Callback() {
@Override public void onSuccess() {
super.onSuccess();
}
@Override public void onError(Exception e) {
super.onError(e);
}
});

見た目をもうちょっとだけスッキリさせたい場合は、 Realm.Transaction.Callback オブジェクトの Wrapper クラスを用意してみましょう。

public class SimpleCallback extends Realm.Transaction.Callback {

private Result mResult;

public SimpleCallback(Result result) {
mResult = result;
}

@Override public void onSuccess() {
super.onSuccess();
mResult.onResult(true, null);
}

@Override public void onError(Exception e) {
super.onError(e);
mResult.onResult(false, e);
}

public interface Result {
void onResult(boolean success, @Nullable Exception e);
}
}

このクラスと Retrolambda を使うと、

mRealm.executeTransaction(realm -> {

// realm を使って処理する
}, new SimpleCallback((success, e) -> {
if (success) {
} else {
}
}));

このように書けます。インデントの深さは変わらないですが、見た目はスッキリするのではないでしょうか?


最後に

Realm を Android で使う場合の小さな Tips を書いてみました。 try-finally や Retrolambda は今注目を集めている Kotlin を使えば、さらに便利に書けそうなのでそちらを使ってみるのも良いでしょう:)

Realm.getDefaultInstance().use { realm ->

}

こんな風に書けば try-with-resources みたいに close まで面倒を見てくれるみたいですしね!