SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

一歩進んだAndroidアプリ開発ができる「Android Jetpack」入門

メンテナンスしやすいアプリを作成しよう! RoomをViewModelと組み合わせて利用する方法

一歩進んだAndroidアプリ開発ができる「Android Jetpack」入門 第3回

  • X ポスト
  • このエントリーをはてなブックマークに追加

ViewModel内でデータベースへアクセス

 このMainViewModelにprepareCocktailNote()メソッドを追加するとします。このメソッドは、図1の画面上のリストをタップした時に呼び出されるメソッドであり、このメソッド内でデータベースにアクセスして、タップされたカクテルのデータを呼び出し、そのデータをフィールド/プロパティに格納するメソッドとします。そして、その際にRoomを利用します。すると、Javaではリスト3、Kotlinではリスト4のようなコードになります。

リスト3:MainViewModel#prepareCocktailNote()(Java版)
public void prepareCocktailNote() {
  //フィールドのカクテルメモデータを初期化。
  _cocktailNote = "";
  //DAOオブジェクトを取得。
  CocktailmemoDAO cocktailmemoDAO = _db.createCocktailmemoDAO();  //(1)
  //主キー検索を実行。
  ListenableFuture<Cocktailmemo> future = cocktailmemoDAO.findByPK(_cocktailId);
  try {
    //検索結果を取得。
    Cocktailmemo cocktailmemo = future.get();
    //検索結果がnullじゃなかったら…
    if(cocktailmemo != null) {
      //データベースのカクテルメモデータをフィールドに格納。
      _cocktailNote = cocktailmemo.note;
    }
  }
  catch(ExecutionException ex) {
    :
  }
  catch(InterruptedException ex) {
    :
  }
}
リスト4:MainViewModel#prepareCocktailNote()(Kotlin版)
suspend fun prepareCocktailNote(): Job {  // (1)
  //DAOオブジェクトを取得。
  val cocktailmemoDAO = _db.createCocktailmemoDAO()  // (2)
  //コルーチンスコープの準備。
  val job = viewModelScope.launch {  // (3)
    //主キー検索を実行。
    val cocktailmemo = cocktailmemoDAO.findByPK(cocktailId)
    //データベースのカクテルメモデータをフィールドに格納。
    cocktailNote = cocktailmemo?.note ?: ""
  }
  //コルーチンスコープの戻り値をリターン。
  return job  // (4)
}

 リスト3のJavaコードに関しては、特に問題はないと思います。コメントを頼りにすると理解してもらえるコードだと思います。一方、リスト4のKotlinコードに関して、少し補足しておきます。

 前回紹介したように、KotlinのDAOメソッドは、コルーチンを利用したsuspendメソッドとなっています。このメソッドを利用する場合は、コルーチンスコープが必要になります。前回のリスト10ではこのコルーチンスコープとしてlifecycleScopeのコードを掲載していますが、ViewModel内でコルーチンスコープを用意する場合は、専用のviewModelScopeがあるので、これを利用します。それが、(3)です。

 ただし、prepareCocktailNote()メソッド内でコルーチンスコープを利用した時点で、そのメソッド自身もsuspendメソッドになるので、(1)のようにsuspendキーワードをメソッド宣言に付与します。さらに、このviewModelScopeのlaunchの戻り値であるJobオブジェクトをこのメソッドの戻り値としてリターンしておきます。それが(4)です。こうすることで、このメソッドを利用するMainActivityでは、リスト5のコードを記述することで、prepareCocktailNote()メソッド内の処理終了を待つことができ、その結果、データベースから取得した値を画面表示に利用できるようになります。

リスト5:MainActivity(Kotlin版)
lifecycleScope.launch {
  val job = _mainViewModel.prepareCocktailNote()
  job.join()
    :
  etNote.setText(_mainViewModel.cocktailNote)
}

[NOTE]アクティビティのコルーチンスコープは避けるべき

 ここで紹介したように、ViewModelのメソッド内でコルーチンスコープを利用するためにsuspendメソッドとして、アクティビティ内でさらなるコルーチンスコープを用意するコーディングパターンは、あまりよくありません。そもそも、たとえlifecycleScopeというコルーチンスコープが用意されていても、アクティビティでのコルーチンの実行は避けるべきです。なぜなら、アクティビティではライフサイクルへの依存が発生し、コルーチンの中断、開始の適切な処理が難しいからです。そのため、コルーチンの実行はViewModelに任せます。ただし、ViewModelでコルーチンの実行を完結させようとすると、Flowという仕組みを利用する必要があります。こちらに関しては、次回紹介します。

アプリケーションコンテキストが必要な_db

 ここまでの内容で、すでに問題が含まれています。それは、リスト3の(1)やリスト4の(2)の_dbをどのように用意するかということです。この_dbは、ViewModelクラスのフィールドで保持したRoom Database(AppDatabase)オブジェクトを表します。

 そして、前回のリスト9やリスト10で紹介したように、このRoom Databaseのインスタンスの取得には、アプリケーションコンテキストが必要です。これは、前回のリスト7やリスト8にあるシングルトンパターンでも同じであり、getDatabase()の引数として同じくアプリケーションコンテキストを渡す必要があります。そして、このアプリケーションコンテキストを、ViewModel内では用意できないという問題があります。

Applicationが利用できるAndroidViewModel

 この問題を解決するために、AndroidではApplicationオブジェクトを利用できるViewModelとして、AndroidViewModelクラスが用意されています。そのため、ViewModelクラスを、AndroidViewModelクラスを継承したものとし、Javaコードとしては、リスト6のようなものになります。

リスト6:正しいMainViewModel(Java版)
public class MainViewModel extends AndroidViewModel {  // (1)
  private AppDatabase _db;  // (2)
  private int _cocktailId = -1;
    :
  public MainViewModel(Application application) {  // (3)
    super(application);  // (4)
    _db = AppDatabase.getDatabase(application);  // (5)
  }
  public void prepareCocktailNote() {
    :
  }
  //以下アクセサメソッド
    :
}

 まず、リスト6の(1)のようにAndroidViewModelを継承したクラスとします。そして、このAndroidViewModelを継承した途端、Applicationオブジェクトを引数とするコンストラクタを定義し、内部で親クラスのコンストラクタを呼び出す必要があります。それが、(3)と(4)です。併せて、この引数のApplicationオブジェクトを利用して、AppDatabaseオブジェクトを取得し、(2)のフィールドに格納するコードをコンストラクタに記述します。それが(5)です。このコードパターンを利用することで、ViewModelクラス内でもRoom Database(AppDatabase)オブジェクトが利用できるようになります。

 このコードパターンは、Kotlinでも同じであり、リスト7のコードとなります。リスト6のJavaコードをそのままKotlinコードに置き換えた内容ですので、特に問題ないでしょう。

リスト7:正しいMainViewModel(Kotlin版)
class MainViewModel(application: Application) : AndroidViewModel(application) {
  private val _db: AppDatabase
  var cocktailId = -1
    :
  init {
    _db = AppDatabase.getDatabase(application)
  }
  suspend fun prepareCocktailNote(): Job {
    :
  }
}

[NOTE]AndroidViewModelの利用も避けるべき??

 Android開発者向けの公式ドキュメントには、アプリのアーキテクチャを考える上での推奨事項が記載されたページがあります。そのうちのViewModelに関するセクションでは、実は、このAndroidViewModelの利用はあまり推奨されていません。これはそもそも、ViewModelが、コンテキストからは自由であるべきという考え方に基づいているからです。Applicationオブジェクトを利用する段階で、どうしてもコンテキストに依存してしまいます。

 ただし、これを解決するためには、HiltのようなDIライブラリを別途利用する必要が出てきます。今回は、そこまでの内容を紹介できないことをご了承ください。

次のページ
データ処理を一括に担うリポジトリ

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
一歩進んだAndroidアプリ開発ができる「Android Jetpack」入門連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 齊藤 新三(サイトウ シンゾウ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 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本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/17569 2023/04/05 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング