データ処理を一括に担うリポジトリ
前節で、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を紹介します。