データ処理を一括に担うリポジトリ
前節で、ViewModelからRoomが利用できるようになり、データベースのデータをViewModelから扱えるようになりました。ただし、この状態でも問題があります。次にその話をしていきます。
リポジトリの役割
Androidアプリでは、ViewModelから直接Roomの処理を行うコードパターンは、推奨されていません。つまり、ここで紹介した単一テーブルの簡単なアプリの場合はよいのですが、テーブル数が複数になる、すなわち、DAOオブジェクトが複数になると、途端にViewModel内のデータ処理コードが煩瑣になります。さらに、データベースだけでなく、Web APIとの連携も含めたアプリとなると、さらに複雑なコードになります(図2)。
そもそも、ViewModelはアクティビティに必要なデータを保持するクラスであって、データ処理クラスではありません。そこで、このようなViewModelに必要なデータを、データベースやWeb APIなどのデータソースから取得したり、格納したりする処理をまとめておくクラスを別に用意すべきです。Androidアプリ開発では、このクラスのことをリポジトリと呼んでいます(図3)。そして、今回のアプリのように、たとえデータソースがひとつのテーブルのみでも、リポジトリを導入することが推奨されています。
もちろん、このリポジトリは複数存在することができます。その際、取得するデータの種類に応じてひとつずつ作成することが推奨されています。たとえば、会員に関するデータ処理をまとめておける会員リポジトリと注文に関するデータ処理をまとめておける注文リポジトリなどです。もちろん、それぞれのリポジトリが、さらに、複数のデータソースを利用する形になります。また、アクティビティとViewModelのペアも複数存在できるため、アプリ全体としては、図4のような構造になります。
リポジトリクラスは普通のクラス
では、早速そのリポジトリクラスを導入していきます。実は、ViewModelクラスやRoomの各クラスと違い、リポジトリクラスは普通のクラスとして作成すればよいのです。例えば、カクテルメモ情報に関するデータ処理を行うリポジトリクラスとしてCocktailmemoRepositoryを作成すると、Javaコードではリスト8のコードとなります。
public class CocktailmemoRepository { // (1)
private AppDatabase _db; // (2)
public CocktailmemoRepository(Application application) { // (3)
_db = AppDatabase.getDatabase(application); // (4)
}
public Cocktailmemo getCocktailmemo(int cocktailId) { // (5)
CocktailmemoDAO cocktailmemoDAO = _db.createCocktailmemoDAO();
ListenableFuture<Cocktailmemo> future = cocktailmemoDAO.findByPK(cocktailId);
Cocktailmemo cocktailmemo = null;
try {
cocktailmemo = future.get();
}
catch(ExecutionException ex) {
:
}
catch(InterruptedException ex) {
:
}
return cocktailmemo;
}
}
リスト8の(1)をみてもわかるように、何かのクラスを継承したり、何かのインターフェースを実装したりといった特別なことが何もない、普通のクラスとして宣言します。そして、このリポジトリクラス内でRoomの利用コードを記述することになるので、(2)のように、フィールドでAppDatabaseを宣言し、(4)のようにコンストラクタ内でAppDatabaseオブジェクトを取得します。
ただし、その際、やはりApplicationオブジェクトが必要であり、当然ですが、このクラス内では用意できません。そこで、(3)のようにコンストラクタの引数として定義し、このリポジトリクラスを利用するViewModelクラスから受け取るようにします。
この処理コードさえ記述しておけば、あとは、(5)のように、Roomを利用して、カクテル情報を取得するメソッドなどを定義しておけばよいです。
Kotlinコードでも同じであり、リスト9のようになります。リスト7のJavaコードを、Kotlinコードに置き換えただけのようなコードになっています。ただし、(1)のように、DAOメソッドを実行するメソッドでは、コルーチンスコープは不要であり、単にメソッド全体をsuspendメソッドとするだけでよいです。コルーチンスコープによる処理は、ViewModelに任せます。
class CocktailmemoRepository(application: Application) {
private val _db: AppDatabase
init {
_db = AppDatabase.getDatabase(application)
}
suspend fun getCocktailmemo(cocktailId: Int): Cocktailmemo? { // (1)
val cocktailmemoDAO = _db.createCocktailmemoDAO()
return cocktailmemoDAO.findByPK(cocktailId)
}
}
ViewModelでのリポジトリの利用
リポジトリが用意できたところで、ViewModelを、リポジトリを利用したものへと改造すると、Javaではリスト10のコードとなります。
public class MainViewModel extends AndroidViewModel {
private CocktailmemoRepository _cocktailmemoRepository; // (1)
private int _cocktailId = -1;
:
public MainViewModel(Application application) {
super(application);
_cocktailmemoRepository = new CocktailmemoRepository(application); // (2)
}
public void prepareCocktailNote() {
_cocktailNote = "";
Cocktailmemo cocktailmemo = _cocktailmemoRepository.getCocktailmemo(_cocktailId); // (3)
if(cocktailmemo != null) { // (4)
_cocktailNote = cocktailmemo.note;
}
}
//以下アクセサメソッド
:
}
リスト10では、(1)のように、これまでフィールドとして保持していたAppDatabaseの代わりに、CocktailmemoRepositoryをフィールドとしています。そして、そのインスタンスの生成を、(2)のようにコンストラクタ内で行っています。これは、リスト8の(3)のように、リポジトリクラスのコンストラクタの引数としてApplicationが定義されており、これを渡すタイミングが(2)のようにコンストラクタ内しかないからです。
このように、フィールドにリポジトリオブジェクトを用意しておくと、あとは、(3)のように、リポジトリのメソッドを実行することで必要なデータを用意することができます。なお、リスト8の(5)のgetCocktailmemo()メソッド内のコードからもわかるように、getCocktailmemo()メソッドの戻り値であるCocktailmemoオブジェクトはnullの可能性があります。そこを考慮して、(4)のようにnullチェックを行なっています。
Kotlinコードも同様で、リスト11のようになります。このコードは特に問題ないでしょう。
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val _cocktailmemoRepository: CocktailmemoRepository
var cocktailId = -1
:
init {
_cocktailmemoRepository = CocktailmemoRepository(application)
}
suspend fun prepareCocktailNote(): Job {
val job =viewModelScope.launch {
val cocktailmemo = _cocktailmemoRepository.getCocktailmemo(cocktailId)
cocktailNote = cocktailmemo?.note ?: ""
}
return job
}
}
まとめ
Android Jetpackについて紹介していく本連載の第3回は、いかがでしたでしょうか。
今回は、RoomをViewModelと組み合わせて利用する方法を紹介しました。さらに、よりメンテナンスしやすいアプリを作成するためのアーキテクチャであるリポジトリも、併せて紹介しました。
次回は、非同期でのデータ更新を、そのタイミングに合わせて画面に反映させる仕組みであるLiveDataとFlowを紹介します。
