この 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: スターター アプリをダウンロードして実行する
- GitHub から TrackMySleepQuality-Coroutines-Starter アプリをダウンロードします。
- アプリをビルドして実行します。このアプリには
SleepTrackerFragment
フラグメントの UI が表示されますが、データは表示されません。ボタンがタップに反応しない。
ステップ 2: コードを調べる
この Codelab のスターター コードは、6.1 Room データベースの作成 Codelab の解答コードと同じです。
- res/layout/activity_main.xmlこのレイアウトには
nav_host_fragment
フラグメントが含まれています。また、<merge>
タグに注目してください。merge
タグを使用すると、レイアウトを含める際の冗長なレイアウトをなくすことができます。このタグを使用することをおすすめします。冗長レイアウトの例としては、ConstraintLayout > LinearLayout > TextView があります。この場合、LinearLayout はシステムによって削除される可能性があります。この種の最適化によって、ビュー階層がシンプルになり、アプリのパフォーマンスが改善されます。 - navigation フォルダで、navigation.xml を開きます。2 つのフラグメントと、それを接続するナビゲーション アクションが表示されます。
- [layout] フォルダで、スリープ トラッカー フラグメントをダブルクリックすると、XML レイアウトが表示されます。次の点に注意してください。
- レイアウト データは
<layout>
要素でラップされ、データ バインディングが有効になります。 ConstraintLayout
などのビューは<layout>
要素内に配置されます。- このファイルにはプレースホルダの
<data>
タグがあります。
スターター アプリは、UI のディメンション、色、スタイルも提供します。アプリには、Room
データベース、DAO、SleepNight
エンティティが含まれます。前の Codelab を完了していない場合は、コードのこの側面をご自身で確認してください。
ここまでで、データベースと UI を用意しました。次は、データを収集してデータベースにデータを追加し、データを表示します。この作業はすべてビューモデルで行います。睡眠トラッカー ビューモデルは、ボタンのクリックを処理し、DAO を介してデータベースを操作し、LiveData
を介して UI にデータを提供します。データベース操作はすべてメイン UI スレッドから切り離す必要があるため、コルーチンを使用します。
ステップ 1: SleepTrackerViewModel を追加する
- sleeptracker パッケージで SleepTrackerViewModel.kt を開きます。
- スターター アプリで提供されている
SleepTrackerViewModel
クラスを確認します。このクラスは次のようになっています。クラスがAndroidViewModel()
を拡張していることに注意してください。このクラスはViewModel
と同様ですが、アプリケーション コンテキストをパラメータとして受け取り、プロパティとして利用できるようにします。これは後で必要になります。
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application) : AndroidViewModel(application) {
}
ステップ 2: SleepTrackerViewModelFactory を追加する
- sleeptracker パッケージで SleepTrackerViewModelFactory.kt を開きます。
- ファクトリー用に提供されているコード(下記参照)を調べます。
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 を更新する
SleepTrackerFragment
で、アプリ コンテキストへの参照を取得します。参照はbinding
の下のonCreateView()
に配置します。ビューモデル ファクトリ プロバイダに渡すには、このフラグメントがアタッチされているアプリへの参照が必要です。
value がnull
の場合、requireNotNull
Kotlin 関数はIllegalArgumentException
をスローします。
val application = requireNotNull(this.activity).application
- DAO への参照を介したデータソースへの参照が必要です。
onCreateView()
のreturn
の前にdataSource
を定義します。データベースの DAO への参照を取得するには、SleepDatabase.getInstance(application).sleepDatabaseDao
を使用します。
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
onCreateView()
のreturn
の前にviewModelFactory
のインスタンスを作成します。dataSource
とapplication
を渡す必要があります。
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- ファクトリーが完成したので、
SleepTrackerViewModel
への参照を取得します。SleepTrackerViewModel::class.java
パラメータは、このオブジェクトのランタイム Java クラスを参照します。
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
- 完成したコードは次のようになります。
// 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
レイアウト ファイル内:
<data>
ブロック内で、SleepTrackerViewModel
クラスを参照する<variable>
を作成します。
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>
SleepTrackerFragment
で次のように変更します。
- 現在のアクティビティをバインディングのライフサイクル オーナーとして設定します。次のコードを
onCreateView()
メソッド内のreturn
ステートメントの前に追加します。
binding.setLifecycleOwner(this)
sleepTrackerViewModel
バインディング変数をsleepTrackerViewModel
に割り当てます。次のコードをonCreateView()
内、SleepTrackerViewModel
を作成するコードの下に配置します。
binding.sleepTrackerViewModel = sleepTrackerViewModel
- バインディング オブジェクトを再作成する必要があるため、おそらくエラーが表示されます。エラーを解消するには、プロジェクトをクリーンアップして再ビルドします。
- 最後に、コードをビルドし、エラーなく実行していることを確認します。
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
の作成や更新などのデータベース オペレーションがトリガーされます。このため、コルーチンを使用して、アプリボタンのクリック ハンドラを実装します。
- アプリレベルの
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"
SleepTrackerViewModel
ファイルを開きます。- クラスの本文で、
viewModelJob
を定義して、それにJob
のインスタンスを割り当てます。このviewModelJob
を使用すると、ビューモデルが使用されなくなり、破棄されたときに、このビューモデルによって開始されたすべてのコルーチンをキャンセルできます。このようにすると、コルーチンはどこにも戻るコルーチンがありません。
private var viewModelJob = Job()
- クラスの本体の最後にある
onCleared()
をオーバーライドして、すべてのコルーチンをキャンセルします。ViewModel
が破棄されると、onCleared()
が呼び出されます。
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
viewModelJob
の定義のすぐ下にあるコルーチンのuiScope
を定義します。スコープによって、コルーチンを実行するスレッドが決まります。また、このスコープはジョブについて認識する必要があります。スコープを取得するには、CoroutineScope
のインスタンスをリクエストし、ディスパッチャとジョブを渡します。
Dispatchers.Main
を使用すると、uiScope
で起動されたコルーチンがメインスレッドで実行されるようになります。これは、ViewModel
によって開始された多くのコルーチンにとって合理的です。これらのコルーチンがなんらかの処理を行った後、UI を更新するためです。
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
uiScope
の定義の下で、現在の夜を保持するためのtonight
という変数を定義します。データを監視して変更する必要があるため、変数をMutableLiveData
にします。
private var tonight = MutableLiveData<SleepNight?>()
- できるだけ早く
tonight
変数を初期化するには、tonight
の定義の下にinit
ブロックを作成し、initializeTonight()
を呼び出します。initializeTonight()
は次のステップで定義します。
init {
initializeTonight()
}
init
ブロックの下に、initializeTonight()
を実装します。uiScope
でコルーチンを起動します。その内で、getTonightFromDatabase()
を呼び出してデータベースからtonight
の値を取得し、その値をtonight.value
に割り当てます。getTonightFromDatabase()
は次のステップで定義します。
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
getTonightFromDatabase()
を実装します。現在開始されているSleepNight
がない場合、null 値許容SleepNight
を返すprivate suspend
関数として定義します。この関数はなんらかのエラーを返す必要があるため、このままではエラーが残ります。
private suspend fun getTonightFromDatabase(): SleepNight? { }
-
getTonightFromDatabase()
の関数本体内で、Dispatchers.IO
コンテキストで実行されるコルーチンから結果を返します。データベースからデータを取得することは I/O オペレーションであり、UI とは関係がないため、I/O ディスパッチャを使用します。
return withContext(Dispatchers.IO) {}
- リターン ブロック内で、コルーチンがデータベースから今夜(最も新しい夜)を取得します。開始時間と終了時間が同じでない場合、つまり夜間がすでに完了している場合は、
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()
と非常によく似ています。
onStartTracking()
の関数定義から始めます。SleepTrackerViewModel
ファイルのonCleared()
の上にクリック ハンドラを配置できます。
fun onStartTracking() {}
- UI を続行して更新するためにこの結果が必要となるため、
onStartTracking()
内で、uiScope
でコルーチンを起動します。
uiScope.launch {}
- コルーチンの起動内で、新しい
SleepNight
を作成します。これにより、現在の時刻が開始時刻として取得されます。
val newNight = SleepNight()
- コルーチンの起動中でも、
insert()
を呼び出してnewNight
をデータベースに挿入します。このinsert()
suspend 関数がまだ定義されていないため、エラーが表示されます。(これは同じ名前の DAO 関数ではありません)。
insert(newNight)
- また、コルーチンの起動中に、
tonight
を更新します。
tonight.value = getTonightFromDatabase()
onStartTracking()
の下で、SleepNight
を引数として取るprivate suspend
関数としてinsert()
を定義します。
private suspend fun insert(night: SleepNight) {}
insert()
の本文の場合、I/O コンテキストでコルーチンを起動し、DAO からinsert()
を呼び出してデータベースにナイトを挿入します。
withContext(Dispatchers.IO) {
database.insert(night)
}
fragment_sleep_tracker.xml
レイアウト ファイルで、先ほど設定したデータ バインディングのマジックを使用して、onStartTracking()
のクリック ハンドラをstart_button
に追加します。@{() ->
関数表記は、引数を取らずにsleepTrackerViewModel
のクリック ハンドラを呼び出すラムダ関数を作成します。
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
- アプリをビルドして実行します。[開始] ボタンをタップします。この操作を行うと、データが作成されますが、まだ何も表示されません。これを修正します。
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
マップを使用します。
Util.kt
ファイルを開き、formatNights()
と関連するimport
ステートメントの定義に関するコードのコメントを解除します。Android Studio でコードのコメント化を解除するには、//
とマークされたすべてのコードを選択し、Cmd+/
またはControl+/
キーを押します。formatNights()
は HTML 形式の文字列であるタイプSpanned
を返します。- strings.xml を開きます。
CDATA
を使用して、睡眠データを表示するための文字列リソースをフォーマットします。 - SleepTrackerViewModel を開きます。
SleepTrackerViewModel
クラスのuiScope
の定義の下で、nights
という変数を定義します。データベースからすべての宿泊情報を取得して、nights
変数に割り当てます。
private val nights = database.getAllNights()
nights
の定義のすぐ下に、nights
をnightsString
に変換するコードを追加します。Util.kt
のformatNights()
関数を使用します。nights
をTransformations
クラスのmap()
関数に渡します。文字列リソースにアクセスするには、マッピング関数をformatNights()
の呼び出しとして定義します。nights
オブジェクトとResources
オブジェクトを指定します。
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
fragment_sleep_tracker.xml
レイアウト ファイルを開きます。TextView
のandroid:text
プロパティで、リソース文字列をnightsString
への参照に置き換えることができるようになりました。
"@{sleepTrackerViewModel.nightsString}"
- コードを再ビルドしてアプリを実行します。これで、すべての睡眠データが開始時間とともに表示されます。
- [Start] ボタンを数回タップすると、表示されるデータが増えます。
次のステップでは、停止ボタンの機能を有効にします。
ステップ 4: 停止ボタンのクリック ハンドラを追加する
前のステップと同じパターンを使用して、SleepTrackerViewModel.
の停止ボタンのクリック ハンドラを実装します。
onStopTracking()
をViewModel
に追加します。uiScope
でコルーチンを起動します。終了時間がまだ設定されていない場合は、endTimeMilli
を現在のシステム時間に設定し、夜間データでupdate()
を呼び出します。
Kotlin では、return@
label
構文は、このステートメントから返される関数をいくつかのネストされた関数の中で指定します。
fun onStopTracking() {
uiScope.launch {
val oldNight = tonight.value ?: return@launch
oldNight.endTimeMilli = System.currentTimeMillis()
update(oldNight)
}
}
insert()
の実装と同じパターンを使用してupdate()
を実装します。
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
- クリック ハンドラを UI に接続するには、
fragment_sleep_tracker.xml
レイアウト ファイルを開き、クリック ハンドラをstop_button
に追加します。
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
- アプリをビルドして実行します。
- [開始]、[停止] の順にタップします。開始時間、終了時間、睡眠の質(値なし)、睡眠時間が表示されます。
ステップ 5: [クリア] ボタンのクリック ハンドラを追加する
- 同様に、
onClear()
とclear()
を実装します。
fun onClear() {
uiScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}
- クリック ハンドラを UI に接続するには、
fragment_sleep_tracker.xml
を開き、クリック ハンドラをclear_button
に追加します。
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
- アプリをビルドして実行します。
- [消去] をタップすると、データをすべて削除できます。次に、[開始]、[停止] をタップして新しいデータを作成します。
Android Studio プロジェクト: TrackMySleepQualityCoroutines
ViewModel
、ViewModelFactory
、およびデータ バインディングを使用して、アプリの UI アーキテクチャを設定します。- UI がスムーズに動作するように、長時間実行されるタスク(すべてのデータベース オペレーションなど)にはコルーチンを使用します。
- コルーチンは非同期かつ非ブロックであり、
suspend
関数を使用して、非同期コードを順次処理します。 - コルーチンが
suspend
とマークされた関数を呼び出すと、その関数は通常の関数呼び出しのように返されるまでブロックするのではなく、結果の準備が整うまで実行を停止します。その後、中断したところから再開します。 - ブロックと停止の違いは、スレッドがブロックされても他の処理は行われません。スレッドが停止されると、結果が利用可能になるまで他の処理が行われます。
コルーチンを起動するには、ジョブ、ディスパッチャ、スコープが必要です。
- 基本的に、ジョブではキャンセルできます。すべてのコルーチンにジョブがあり、ジョブを使用してコルーチンをキャンセルできます。
- ディスパッチャは、さまざまなスレッドで実行するコルーチンを送信します。
Dispatcher.Main
はメインスレッドでタスクを実行し、Dispartcher.IO
はブロックする I/O タスクを共有スレッドのプールにオフロードします。 - スコープは、ジョブとディスパッチャの情報を組み合わせて、コルーチンを実行するコンテキストを定義します。スコープはコルーチンを追跡します。
データベース操作をトリガーするクリック ハンドラを実装するには、次のパターンを使用します。
- 結果が UI に影響するため、メインスレッドまたは UI スレッドで実行されるコルーチンを起動します。
- サスペンド関数を呼び出して長時間実行処理を行い、結果を待つ間 UI スレッドをブロックしないようにします。
- 長時間実行の処理は UI とは無関係なので、I/O コンテキストに切り替えてください。これにより、作業は最適化され、これらの処理のために確保されたスレッドプールで実行できます。
- 次に、データベース関数を呼び出して処理を実行します。
Transformations
マップを使用して、オブジェクトが変更されるたびに LiveData
オブジェクトから文字列を作成します。
Udacity コース:
Android デベロッパー向けドキュメント:
その他のドキュメントと記事:
- 工場のパターン
- コルーチン Codelab
- コルーチン(公式ドキュメント)
- コルーチンのコンテキストとディスパッチャ
Dispatchers
- Android の制限速度を超える
Job
launch
- Kotlin で戻り、ジャンプする
- CDATA は文字データの略です。CDATA は、これらの文字列の間にあるデータが XML マークアップとして解釈できるものの、解釈されるべきではないデータを含んでいることを意味します。
このセクションでは、インストラクターが主導するコースの一環として、この Codelab に取り組む生徒の課題について説明します。教師は以下のことを行えます。
- 必要に応じて課題を割り当てます。
- 宿題の提出方法を生徒に伝える。
- 宿題を採点します。
教師はこれらの提案を少しだけ使うことができます。また、他の課題は自由に割り当ててください。
この Codelab にご自分で取り組む場合は、これらの課題を使用して知識をテストしてください。
問題への質問
問題 1
コルーチンの利点は次のうちどれですか。
- 非ブロック
- 非同期で実行されます。
- メインスレッド以外のスレッドで実行できる。
- アプリの実行が速くなる。
- 例外を適用することもできます。
- 線形コードとして読み書きできます。
質問 2
suspend 関数とは
suspend
キーワードでアノテーションされた通常の関数。- コルーチン内で呼び出すことができる関数。
- suspend 関数が実行されている間、呼び出し元のスレッドは停止されます。
- suspend 関数は常にバックグラウンドで実行する必要があります。
問題 3
スレッドのブロックと停止の違い該当するものをすべて選択してください。
- 実行をブロックすると、ブロックされたスレッドでは他の処理を実行できない。
- 実行を中断すると、スレッドはオフロードされたタスクの完了を待機している間に他の処理を実行できる。
- 停止はより効率的です。スレッドが待機せず、何もしない可能性があるためです。
- ブロックした場合も中断した場合も、コルーチンの結果が返されるのを待ってから実行が再開されます。
次のレッスンに進む:
このコースの他の Codelab へのリンクについては、Android Kotlin の基礎 Codelab ランディング ページをご覧ください。