メンテナンスしやすいアプリを作成しよう! RoomをViewModelと組み合わせて利用する方法
一歩進んだAndroidアプリ開発ができる「Android Jetpack」入門 第3回
データ処理を一括に担うリポジトリ
前節で、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を紹介します。
この記事は参考になりましたか?
既に会員の方はを行ってください。
- 一歩進んだAndroidアプリ開発ができる「Android Jetpack」入門連載記事一覧
- この記事の著者
-
WINGSプロジェクト 齊藤 新三(サイトウ シンゾウ)
<WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook <個人紹介>WINGSプロジェクト所属のテクニカルライター。Web系製作会社のシステム部門、SI会社を経てフリーランスとして独立。屋号はSarva(サルヴァ)。HAL大阪の非常勤講師を兼務。
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
-
山田 祥寛(ヤマダ ヨシヒロ)
静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数。
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
この記事は参考になりましたか?
この記事をシェア
