Android Kotlin の基礎 06.2: コルーチンと Room

この Codelab は、Android Kotlin の基礎コースの一部です。Codelab を順番に進めていくと、このコースを最大限に活用できます。すべてのコース Codelab は Android Kotlin の基礎 Codelab ランディング ページに掲載されています。

はじめに

完璧なユーザー エクスペリエンスを実現するための最優先事項の一つは、UI が常にレスポンシブで、スムーズに動作するようにすることです。UI のパフォーマンスを向上させる 1 つの方法は、データベース オペレーションなどの長時間実行タスクをバックグラウンドに移行することです。

この Codelab では、Kotlin コルーチンを使用してメインスレッドから離れてデータベース オペレーションを実行する TrackMySleepQuality アプリのユーザー向けの部分を実装します。

前提となる知識

以下について把握しておく必要があります。

  • アクティビティ、フラグメント、ビュー、クリック ハンドラを使用して基本的なユーザー インターフェース(UI)を作成する。
  • フラグメント間を移動し、safeArgs を使用してフラグメント間で単純なデータを渡す。
  • モデル、モデル ファクトリ、変換、LiveData を表示する。
  • Room データベースを作成し、DAO を作成してエンティティを定義する方法。
  • スレッド化とマルチプロセッシングのコンセプトに精通している場合は役立ちます。

学習内容

  • Android でのスレッドの仕組み
  • Kotlin コルーチンを使用してデータベース オペレーションをメインスレッドから移行する方法。
  • TextView でフォーマットされたデータを表示する方法。

演習内容

  • TrackMySleepQuality アプリを拡張して、データベースでのデータの取り込み、保存、表示を行います。
  • コルーチンを使用して、長時間実行されるデータベース オペレーションをバックグラウンドで実行します。
  • LiveData を使用して、ナビゲーションとスナックバーの表示をトリガーします。
  • ボタンを有効または無効にするには LiveData を使用します。

この Codelab では、TrackMySleepQuality アプリのビューモデル、コルーチン、データ表示部分を作成します。

アプリには、下図に示すように、フラグメントで表される画面が 2 つあります。

左側の最初の画面には、トラッキングを開始および停止するボタンがあります。画面には、ユーザーのすべての睡眠データが表示されます。[消去] ボタンをクリックすると、そのアプリがユーザーのために収集したすべてのデータが完全に削除されます。

右の 2 番目の画面では、睡眠の質の評価を選択しています。このアプリでは、レーティングは数値で表示されます。アプリでは、顔アイコンとそれに対応する数字の両方が表示されます。

ユーザーのフローは次のようになります。

  • アプリを開くと、睡眠管理画面が表示されます。
  • ユーザーが [開始] ボタンをタップします。これにより、開始時間が記録されて表示されます。[スタート] ボタンが無効になり、[停止] ボタンが有効になります。
  • ユーザーが [停止] ボタンをタップします。終了時間を記録し、睡眠の質の画面を表示します。
  • ユーザーが睡眠品質のアイコンを選択します。画面が終了し、トラッキング画面に睡眠時間と睡眠品質が表示されます。[停止] ボタンは無効になり、[開始] ボタンは有効になっています。アプリの準備はもう終わりです。
  • データベースにデータが格納されている場合は常に、[クリア] ボタンが有効になります。ユーザーが [消去] ボタンをタップしても、すべてのデータが消去されずに消去されます。「よろしいですか?」というメッセージはありません。

このアプリは、完全なアーキテクチャのコンテキストに示すように、簡略化されたアーキテクチャを使用しています。アプリは次のコンポーネントのみを使用します。

  • UI コントローラ
  • モデルと LiveData を表示します
  • Room データベース

このタスクでは、TextView を使用して、フォーマットされた睡眠記録データを表示します。(これが最終版インターフェースではありません。別の Codelab でさらに学習できます)。

前の Codelab で作成した TrackMySleepQuality アプリのを続けるか、この Codelab のスターター アプリをダウンロードできます。

ステップ 1: スターター アプリをダウンロードして実行する

  1. GitHub から TrackMySleepQuality-Coroutines-Starter アプリをダウンロードします。
  2. アプリをビルドして実行します。このアプリには SleepTrackerFragment フラグメントの UI が表示されますが、データは表示されません。ボタンがタップに反応しない。

ステップ 2: コードを調べる

この Codelab のスターター コードは、6.1 Room データベースの作成 Codelab の解答コードと同じです。

  1. res/layout/activity_main.xmlこのレイアウトには nav_host_fragment フラグメントが含まれています。また、<merge> タグに注目してください。

    merge タグを使用すると、レイアウトを含める際の冗長なレイアウトをなくすことができます。このタグを使用することをおすすめします。冗長レイアウトの例としては、ConstraintLayout > LinearLayout > TextView があります。この場合、LinearLayout はシステムによって削除される可能性があります。この種の最適化によって、ビュー階層がシンプルになり、アプリのパフォーマンスが改善されます。
  2. navigation フォルダで、navigation.xml を開きます。2 つのフラグメントと、それを接続するナビゲーション アクションが表示されます。
  3. [layout] フォルダで、スリープ トラッカー フラグメントをダブルクリックすると、XML レイアウトが表示されます。次の点に注意してください。
  • レイアウト データは <layout> 要素でラップされ、データ バインディングが有効になります。
  • ConstraintLayout などのビューは <layout> 要素内に配置されます。
  • このファイルにはプレースホルダの <data> タグがあります。

スターター アプリは、UI のディメンション、色、スタイルも提供します。アプリには、Room データベース、DAO、SleepNight エンティティが含まれます。前の Codelab を完了していない場合は、コードのこの側面をご自身で確認してください。

ここまでで、データベースと UI を用意しました。次は、データを収集してデータベースにデータを追加し、データを表示します。この作業はすべてビューモデルで行います。睡眠トラッカー ビューモデルは、ボタンのクリックを処理し、DAO を介してデータベースを操作し、LiveData を介して UI にデータを提供します。データベース操作はすべてメイン UI スレッドから切り離す必要があるため、コルーチンを使用します。

ステップ 1: SleepTrackerViewModel を追加する

  1. sleeptracker パッケージで SleepTrackerViewModel.kt を開きます。
  2. スターター アプリで提供されている SleepTrackerViewModel クラスを確認します。このクラスは次のようになっています。クラスが AndroidViewModel() を拡張していることに注意してください。このクラスは ViewModel と同様ですが、アプリケーション コンテキストをパラメータとして受け取り、プロパティとして利用できるようにします。これは後で必要になります。
class SleepTrackerViewModel(
       val database: SleepDatabaseDao,
       application: Application) : AndroidViewModel(application) {
}

ステップ 2: SleepTrackerViewModelFactory を追加する

  1. sleeptracker パッケージで SleepTrackerViewModelFactory.kt を開きます。
  2. ファクトリー用に提供されているコード(下記参照)を調べます。
class SleepTrackerViewModelFactory(
       private val dataSource: SleepDatabaseDao,
       private val application: Application) : ViewModelProvider.Factory {
   @Suppress("unchecked_cast")
   override fun <T : ViewModel?> create(modelClass: Class<T>): T {
       if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
           return SleepTrackerViewModel(dataSource, application) as T
       }
       throw IllegalArgumentException("Unknown ViewModel class")
   }
}

次の点に注目してください。

  • 指定された SleepTrackerViewModelFactory は、ViewModel と同じ引数を取り、ViewModelProvider.Factory を拡張します。
  • ファクトリー内で、このコードが create() をオーバーライドします。これは、任意のクラス型を引数として受け取り、ViewModel を返します。
  • create() の本体で、SleepTrackerViewModel クラスが使用可能であることを確認し、存在する場合、そのインスタンスを返します。それ以外の場合、コードは例外をスローします。

ステップ 3: SleepTrackerFragment を更新する

  1. SleepTrackerFragment で、アプリ コンテキストへの参照を取得します。参照は binding の下の onCreateView() に配置します。ビューモデル ファクトリ プロバイダに渡すには、このフラグメントがアタッチされているアプリへの参照が必要です。

    valuenull の場合、requireNotNull Kotlin 関数は IllegalArgumentException をスローします。
val application = requireNotNull(this.activity).application
  1. DAO への参照を介したデータソースへの参照が必要です。onCreateView()return の前に dataSource を定義します。データベースの DAO への参照を取得するには、SleepDatabase.getInstance(application).sleepDatabaseDao を使用します。
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. onCreateView()return の前に viewModelFactory のインスタンスを作成します。dataSourceapplication を渡す必要があります。
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
  1. ファクトリーが完成したので、SleepTrackerViewModel への参照を取得します。SleepTrackerViewModel::class.java パラメータは、このオブジェクトのランタイム Java クラスを参照します。
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)
  1. 完成したコードは次のようになります。
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)

これまでの onCreateView() メソッドを次に示します。

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        // Get a reference to the binding object and inflate the fragment views.
        val binding: FragmentSleepTrackerBinding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_sleep_tracker, container, false)

        val application = requireNotNull(this.activity).application

        val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao

        val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

        val sleepTrackerViewModel =
                ViewModelProviders.of(
                        this, viewModelFactory).get(SleepTrackerViewModel::class.java)

        return binding.root
    }

ステップ 4: ビューモデルのデータ バインディングを追加する

基本的な ViewModel を設定したら、SleepTrackerFragment でデータ バインディングを設定して、ViewModel を UI に接続する必要があります。


fragment_sleep_tracker.xml レイアウト ファイル内:

  1. <data> ブロック内で、SleepTrackerViewModel クラスを参照する <variable> を作成します。
<data>
   <variable
       name="sleepTrackerViewModel"
       type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>

SleepTrackerFragment で次のように変更します。

  1. 現在のアクティビティをバインディングのライフサイクル オーナーとして設定します。次のコードを onCreateView() メソッド内の return ステートメントの前に追加します。
binding.setLifecycleOwner(this)
  1. sleepTrackerViewModel バインディング変数を sleepTrackerViewModel に割り当てます。次のコードを onCreateView() 内、SleepTrackerViewModel を作成するコードの下に配置します。
binding.sleepTrackerViewModel = sleepTrackerViewModel
  1. バインディング オブジェクトを再作成する必要があるため、おそらくエラーが表示されます。エラーを解消するには、プロジェクトをクリーンアップして再ビルドします。
  2. 最後に、コードをビルドし、エラーなく実行していることを確認します。

Kotlin では、コルーチンは長時間実行タスクをスムーズかつ効率的に処理するための方法です。Kotlin コルーチンを使用すると、コールバック ベースのコードを順次コードに変換できます。一般的に、シーケンシャルに記述されたコードは読みやすく、例外などの言語機能も使用できます。最終的に、コルーチンとコールバックでも同様に、長時間実行タスクから結果が得られるのを待ってから実行を継続します。

コルーチンには次のプロパティがあります。

  • コルーチンは非同期かつ非ブロッキングです。
  • コルーチンは非同期コードを使用して、suspend 関数を使用します。

コルーチンは非同期である。

コルーチンは、プログラムの主な実行ステップとは独立して実行されます。この処理は並列に行うことも、別のプロセッサで行うこともできます。また、アプリの他の部分が入力を待機している間に、少し処理してくれた可能性もあります。非同期の重要な側面の 1 つは、明示的に結果が表示されるまで、結果が利用可能であることを想定できないということです。

たとえば、調査が必要な質問があり、同僚に答えを尋ねるとします。外に出て仕事をすること。これは、「非同期」で、かつ別のスレッドで仕事をしているように見えるのです。同僚が回答を確認して回答を得るまでは、解答に依存しない他の作業を続けることができます。

コルーチンは非ブロッキング。

非ブロッキングとは、コルーチンがメインスレッドまたは UI スレッドをブロックしていないことを意味します。そこで、コルーチンを使用すると、UI インタラクションが常に優先されるため、ユーザーは常にスムーズに操作できるようになります。

コルーチンは suspend 関数を使用して非同期コードを順次処理します。

キーワード suspend は、コルーチンで使用できる関数または関数型に Kotlin でマークを付ける方法です。コルーチンが suspend とマークされた関数を呼び出すと、その関数は通常の関数呼び出しのように返されるまでブロックするのではなく、結果の準備が整うまで実行を停止します。その後、コルーチンは中断したところから再開し、結果を返します。

コルーチンが停止されて結果を待機している間、コルーチンは実行中のスレッドのブロックを解除します。これにより、他の関数やコルーチンを実行できるようになります。

suspend キーワードは、コードが実行されるスレッドを指定しません。suspend 関数は、バックグラウンド スレッドまたはメインスレッドで実行される場合があります。

Kotlin でコルーチンを使用するには、次の 3 つが必要です。

  • ジョブ
  • ディスパッチャ
  • スコープ

Job: 基本的に、ジョブはキャンセルできるものです。すべてのコルーチンにジョブがあり、そのジョブを使用してコルーチンをキャンセルできる。ジョブは親子階層に配置できます。親ジョブをキャンセルすると、すべてのジョブの子がすぐにキャンセルされます。これは、各コルーチンを手動でキャンセルするよりもはるかに便利です。

ディスパッチャ: ディスパッチャがさまざまなスレッドで実行されるように、コルーチンを送信します。たとえば、Dispatcher.Main はメインスレッドでタスクを実行し、Dispatcher.IO は I/O タスクをブロックしてスレッドの共有プールにオフロードします。

スコープ: コルーチンのスコープでは、コルーチンを実行するコンテキストを定義します。スコープは、コルーチンのジョブとディスパッチャに関する情報を組み合わせたものです。スコープはコルーチンを追跡します。コルーチンを起動すると、そのスコープが「コルーチンで追跡するスコープ」を指定することになります。

ユーザーが次の方法で睡眠データを操作できるようにする必要があります。

  • ユーザーが [Start] ボタンをタップすると、新しいスリープ ナイトが作成され、スリープナイトがデータベースに保存されます。
  • ユーザーが [停止] ボタンをタップすると、夜間に終了時刻が更新されます。
  • ユーザーが [消去] ボタンをタップすると、データベース内のデータが消去されます。

これらのデータベース オペレーションには時間がかかることがあるため、別のスレッドで実行する必要があります。

ステップ 1: データベース オペレーションのコルーチンを設定する

Sleep Tracker アプリの [Start] ボタンをタップしたときに、SleepTrackerViewModel の関数を呼び出して SleepNight の新しいインスタンスを作成し、そのインスタンスをデータベースに格納します。

いずれかのボタンをタップすると、SleepNight の作成や更新などのデータベース オペレーションがトリガーされます。このため、コルーチンを使用して、アプリボタンのクリック ハンドラを実装します。

  1. アプリレベルの build.gradle ファイルを開き、コルーチンの依存関係を見つけます。コルーチンを使用するには、これらの依存関係が必要です。

    $coroutine_version は、プロジェクトの build.gradle ファイルで coroutine_version = '1.0.0' として定義されます。
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
  1. SleepTrackerViewModel ファイルを開きます。
  2. クラスの本文で、viewModelJob を定義して、それに Job のインスタンスを割り当てます。この viewModelJob を使用すると、ビューモデルが使用されなくなり、破棄されたときに、このビューモデルによって開始されたすべてのコルーチンをキャンセルできます。このようにすると、コルーチンはどこにも戻るコルーチンがありません。
private var viewModelJob = Job()
  1. クラスの本体の最後にある onCleared() をオーバーライドして、すべてのコルーチンをキャンセルします。ViewModel が破棄されると、onCleared() が呼び出されます。
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}
  1. viewModelJob の定義のすぐ下にあるコルーチンの uiScope を定義します。スコープによって、コルーチンを実行するスレッドが決まります。また、このスコープはジョブについて認識する必要があります。スコープを取得するには、CoroutineScope のインスタンスをリクエストし、ディスパッチャとジョブを渡します。

Dispatchers.Main を使用すると、uiScope で起動されたコルーチンがメインスレッドで実行されるようになります。これは、ViewModel によって開始された多くのコルーチンにとって合理的です。これらのコルーチンがなんらかの処理を行った後、UI を更新するためです。

private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  1. uiScope の定義の下で、現在の夜を保持するための tonight という変数を定義します。データを監視して変更する必要があるため、変数を MutableLiveData にします。
private var tonight = MutableLiveData<SleepNight?>()
  1. できるだけ早く tonight 変数を初期化するには、tonight の定義の下に init ブロックを作成し、initializeTonight() を呼び出します。initializeTonight() は次のステップで定義します。
init {
   initializeTonight()
}
  1. init ブロックの下に、initializeTonight() を実装します。uiScope でコルーチンを起動します。その内で、getTonightFromDatabase() を呼び出してデータベースから tonight の値を取得し、その値を tonight.value に割り当てます。getTonightFromDatabase() は次のステップで定義します。
private fun initializeTonight() {
   uiScope.launch {
       tonight.value = getTonightFromDatabase()
   }
}
  1. getTonightFromDatabase() を実装します。現在開始されている SleepNight がない場合、null 値許容 SleepNight を返す private suspend 関数として定義します。この関数はなんらかのエラーを返す必要があるため、このままではエラーが残ります。
private suspend fun getTonightFromDatabase(): SleepNight? { }
  1. getTonightFromDatabase() の関数本体内で、Dispatchers.IO コンテキストで実行されるコルーチンから結果を返します。データベースからデータを取得することは I/O オペレーションであり、UI とは関係がないため、I/O ディスパッチャを使用します。
  return withContext(Dispatchers.IO) {}
  1. リターン ブロック内で、コルーチンがデータベースから今夜(最も新しい夜)を取得します。開始時間と終了時間が同じでない場合、つまり夜間がすでに完了している場合は、null を返します。それ以外の場合は、夜間を返します。
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night

完成した getTonightFromDatabase() suspend 関数は次のようになります。エラーはなくなるはずです。

private suspend fun getTonightFromDatabase(): SleepNight? {
   return withContext(Dispatchers.IO) {
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night
   }
}

ステップ 2: [スタート] ボタンのクリック ハンドラを追加する

これで、開始ボタンのクリック ハンドラである onStartTracking() を実装できるようになりました。新しい SleepNight を作成してデータベースに挿入し、tonight に割り当てる必要があります。onStartTracking() の構造は、initializeTonight() と非常によく似ています。

  1. onStartTracking() の関数定義から始めます。SleepTrackerViewModel ファイルの onCleared() の上にクリック ハンドラを配置できます。
fun onStartTracking() {}
  1. UI を続行して更新するためにこの結果が必要となるため、onStartTracking() 内で、uiScope でコルーチンを起動します。
uiScope.launch {}
  1. コルーチンの起動内で、新しい SleepNight を作成します。これにより、現在の時刻が開始時刻として取得されます。
        val newNight = SleepNight()
  1. コルーチンの起動中でも、insert() を呼び出して newNight をデータベースに挿入します。この insert() suspend 関数がまだ定義されていないため、エラーが表示されます。(これは同じ名前の DAO 関数ではありません)。
       insert(newNight)
  1. また、コルーチンの起動中に、tonight を更新します。
       tonight.value = getTonightFromDatabase()
  1. onStartTracking() の下で、SleepNight を引数として取る private suspend 関数として insert() を定義します。
private suspend fun insert(night: SleepNight) {}
  1. insert() の本文の場合、I/O コンテキストでコルーチンを起動し、DAO から insert() を呼び出してデータベースにナイトを挿入します。
   withContext(Dispatchers.IO) {
       database.insert(night)
   }
  1. fragment_sleep_tracker.xml レイアウト ファイルで、先ほど設定したデータ バインディングのマジックを使用して、onStartTracking() のクリック ハンドラを start_button に追加します。@{() -> 関数表記は、引数を取らずに sleepTrackerViewModel のクリック ハンドラを呼び出すラムダ関数を作成します。
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
  1. アプリをビルドして実行します。[開始] ボタンをタップします。この操作を行うと、データが作成されますが、まだ何も表示されません。これを修正します。
fun someWorkNeedsToBeDone {
   uiScope.launch {

        suspendFunction()

   }
}

suspend fun suspendFunction() {
   withContext(Dispatchers.IO) {
       longrunningWork()
   }
}

ステップ 3: データを表示する

DAO の getAllNights()LiveData を返すので、SleepTrackerViewModel では、nights 変数は LiveData を参照します。

これは、データベース内のデータが変更されるたびに、LiveData nights が更新されて最新のデータを表示する Room 機能です。LiveData を明示的に設定したり更新したりする必要はありません。Room は、データベースと一致するようにデータを更新します。

ただし、nights をテキストビューで表示した場合は、オブジェクト参照が表示されます。オブジェクトのコンテンツを表示するには、データをフォーマットされた文字列に変換します。nights がデータベースから新しいデータを受信するたびに実行される Transformation マップを使用します。

  1. Util.kt ファイルを開き、formatNights() と関連する import ステートメントの定義に関するコードのコメントを解除します。Android Studio でコードのコメント化を解除するには、// とマークされたすべてのコードを選択し、Cmd+/ または Control+/ キーを押します。
  2. formatNights() は HTML 形式の文字列であるタイプ Spanned を返します。
  3. strings.xml を開きます。CDATA を使用して、睡眠データを表示するための文字列リソースをフォーマットします。
  4. SleepTrackerViewModel を開きます。SleepTrackerViewModel クラスの uiScope の定義の下で、nights という変数を定義します。データベースからすべての宿泊情報を取得して、nights 変数に割り当てます。
private val nights = database.getAllNights()
  1. nights の定義のすぐ下に、nightsnightsString に変換するコードを追加します。Util.ktformatNights() 関数を使用します。

    nightsTransformations クラスの map() 関数に渡します。文字列リソースにアクセスするには、マッピング関数を formatNights() の呼び出しとして定義します。nights オブジェクトと Resources オブジェクトを指定します。
val nightsString = Transformations.map(nights) { nights ->
   formatNights(nights, application.resources)
}
  1. fragment_sleep_tracker.xml レイアウト ファイルを開きます。TextViewandroid:text プロパティで、リソース文字列を nightsString への参照に置き換えることができるようになりました。
"@{sleepTrackerViewModel.nightsString}"
  1. コードを再ビルドしてアプリを実行します。これで、すべての睡眠データが開始時間とともに表示されます。
  2. [Start] ボタンを数回タップすると、表示されるデータが増えます。

次のステップでは、停止ボタンの機能を有効にします。

ステップ 4: 停止ボタンのクリック ハンドラを追加する

前のステップと同じパターンを使用して、SleepTrackerViewModel.停止ボタンのクリック ハンドラを実装します。

  1. onStopTracking()ViewModel に追加します。uiScope でコルーチンを起動します。終了時間がまだ設定されていない場合は、endTimeMilli を現在のシステム時間に設定し、夜間データで update() を呼び出します。

    Kotlin では、return@label 構文は、このステートメントから返される関数をいくつかのネストされた関数の中で指定します。
fun onStopTracking() {
   uiScope.launch {
       val oldNight = tonight.value ?: return@launch
       oldNight.endTimeMilli = System.currentTimeMillis()
       update(oldNight)
   }
}
  1. insert() の実装と同じパターンを使用して update() を実装します。
private suspend fun update(night: SleepNight) {
   withContext(Dispatchers.IO) {
       database.update(night)
   }
}
  1. クリック ハンドラを UI に接続するには、fragment_sleep_tracker.xml レイアウト ファイルを開き、クリック ハンドラを stop_button に追加します。
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
  1. アプリをビルドして実行します。
  2. [開始]、[停止] の順にタップします。開始時間、終了時間、睡眠の質(値なし)、睡眠時間が表示されます。

ステップ 5: [クリア] ボタンのクリック ハンドラを追加する

  1. 同様に、onClear()clear() を実装します。
fun onClear() {
   uiScope.launch {
       clear()
       tonight.value = null
   }
}

suspend fun clear() {
   withContext(Dispatchers.IO) {
       database.clear()
   }
}
  1. クリック ハンドラを UI に接続するには、fragment_sleep_tracker.xml を開き、クリック ハンドラを clear_button に追加します。
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
  1. アプリをビルドして実行します。
  2. [消去] をタップすると、データをすべて削除できます。次に、[開始]、[停止] をタップして新しいデータを作成します。

Android Studio プロジェクト: TrackMySleepQualityCoroutines

  • ViewModelViewModelFactory、およびデータ バインディングを使用して、アプリの UI アーキテクチャを設定します。
  • UI がスムーズに動作するように、長時間実行されるタスク(すべてのデータベース オペレーションなど)にはコルーチンを使用します。
  • コルーチンは非同期かつ非ブロックであり、suspend 関数を使用して、非同期コードを順次処理します。
  • コルーチンが suspend とマークされた関数を呼び出すと、その関数は通常の関数呼び出しのように返されるまでブロックするのではなく、結果の準備が整うまで実行を停止します。その後、中断したところから再開します。
  • ブロックと停止の違いは、スレッドがブロックされても他の処理は行われません。スレッドが停止されると、結果が利用可能になるまで他の処理が行われます。

コルーチンを起動するには、ジョブ、ディスパッチャ、スコープが必要です。

  • 基本的に、ジョブではキャンセルできます。すべてのコルーチンにジョブがあり、ジョブを使用してコルーチンをキャンセルできます。
  • ディスパッチャは、さまざまなスレッドで実行するコルーチンを送信します。Dispatcher.Main はメインスレッドでタスクを実行し、Dispartcher.IO はブロックする I/O タスクを共有スレッドのプールにオフロードします。
  • スコープは、ジョブとディスパッチャの情報を組み合わせて、コルーチンを実行するコンテキストを定義します。スコープはコルーチンを追跡します。

データベース操作をトリガーするクリック ハンドラを実装するには、次のパターンを使用します。

  1. 結果が UI に影響するため、メインスレッドまたは UI スレッドで実行されるコルーチンを起動します。
  2. サスペンド関数を呼び出して長時間実行処理を行い、結果を待つ間 UI スレッドをブロックしないようにします。
  3. 長時間実行の処理は UI とは無関係なので、I/O コンテキストに切り替えてください。これにより、作業は最適化され、これらの処理のために確保されたスレッドプールで実行できます。
  4. 次に、データベース関数を呼び出して処理を実行します。

Transformations マップを使用して、オブジェクトが変更されるたびに LiveData オブジェクトから文字列を作成します。

Udacity コース:

Android デベロッパー向けドキュメント:

その他のドキュメントと記事:

このセクションでは、インストラクターが主導するコースの一環として、この Codelab に取り組む生徒の課題について説明します。教師は以下のことを行えます。

  • 必要に応じて課題を割り当てます。
  • 宿題の提出方法を生徒に伝える。
  • 宿題を採点します。

教師はこれらの提案を少しだけ使うことができます。また、他の課題は自由に割り当ててください。

この Codelab にご自分で取り組む場合は、これらの課題を使用して知識をテストしてください。

問題への質問

問題 1

コルーチンの利点は次のうちどれですか。

  • 非ブロック
  • 非同期で実行されます。
  • メインスレッド以外のスレッドで実行できる。
  • アプリの実行が速くなる。
  • 例外を適用することもできます。
  • 線形コードとして読み書きできます。

質問 2

suspend 関数とは

  • suspend キーワードでアノテーションされた通常の関数。
  • コルーチン内で呼び出すことができる関数。
  • suspend 関数が実行されている間、呼び出し元のスレッドは停止されます。
  • suspend 関数は常にバックグラウンドで実行する必要があります。

問題 3

スレッドのブロックと停止の違い該当するものをすべて選択してください。

  • 実行をブロックすると、ブロックされたスレッドでは他の処理を実行できない。
  • 実行を中断すると、スレッドはオフロードされたタスクの完了を待機している間に他の処理を実行できる。
  • 停止はより効率的です。スレッドが待機せず、何もしない可能性があるためです。
  • ブロックした場合も中断した場合も、コルーチンの結果が返されるのを待ってから実行が再開されます。

次のレッスンに進む: 6.3 LiveData を使用してボタンの状態を制御する

このコースの他の Codelab へのリンクについては、Android Kotlin の基礎 Codelab ランディング ページをご覧ください。