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

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

はじめに

アプリで完璧なユーザー エクスペリエンスを実現するための最優先事項の 1 つは、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")
   }
}

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

  • 提供された SleepTrackerViewModelFactoryViewModel と同じ引数を取り、ViewModelProvider.Factory を拡張します。
  • ファクトリ内で、コードは create() をオーバーライドします。これは、任意のクラス型を引数として受け取り、ViewModel を返します。
  • create() の本文では、SleepTrackerViewModel クラスが利用可能かどうかを確認し、利用可能な場合はそのインスタンスを返します。そうでない場合、コードは例外をスローします。

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

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

    null の場合、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. 現在の Activity をバインディングのライフサイクル オーナーとして設定します。次のコードを 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 つが必要です。

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

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

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

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

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

  • ユーザーが [開始] ボタンをタップすると、アプリは新しい睡眠夜を作成し、データベースに保存します。
  • ユーザーが [停止] ボタンをタップすると、アプリは終了時刻で夜を更新します。
  • ユーザーが [クリア] ボタンをタップすると、アプリはデータベース内のデータを消去します。

これらのデータベース操作は時間がかかる可能性があるため、別のスレッドで実行する必要があります。

ステップ 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 がない場合は、nullable SleepNight を返す private suspend 関数として定義します。関数は何かを返す必要があるため、このままではエラーが残ります。
private suspend fun getTonightFromDatabase(): SleepNight? { }
  1. getTonightFromDatabase() の関数本体内で、Dispatchers.IO コンテキストで実行されるコルーチンの結果を返します。データベースからデータを取得することは I/O オペレーションであり、UI とは関係がないため、I/O ディスパッチャを使用します。
  return withContext(Dispatchers.IO) {}
  1. return ブロック内で、コルーチンがデータベースから今夜(最新の夜)を取得するようにします。開始時刻と終了時刻が同じでない場合(夜間がすでに完了している場合)、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: [開始] ボタンのクリック ハンドラを追加する

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

  1. まず、onStartTracking() の関数定義から始めます。クリック ハンドラは、SleepTrackerViewModel ファイルの onCleared() の上に配置できます。
fun onStartTracking() {}
  1. onStartTracking() 内で、uiScope でコルーチンを起動します。これは、この結果を使用して UI を更新する必要があるためです。
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. アプリをビルドして実行します。[Start] ボタンをタップします。この操作を行うとデータが作成されますが、まだ何も表示されません。これを修正します。
fun someWorkNeedsToBeDone {
   uiScope.launch {

        suspendFunction()

   }
}

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

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

SleepTrackerViewModel では、DAO の getAllNights()LiveData を返すため、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.kt から formatNights() 関数を使用します。

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

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

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

前の手順と同じパターンを使用して、SleepTrackerViewModel. の [Stop] ボタンのクリック ハンドラを実装します。

  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: [Clear] ボタンのクリック ハンドラを追加する

  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 スレッドがブロックされないように、suspend 関数を呼び出して長時間実行される処理を行います。
  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 のランディング ページをご覧ください。