この 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: スターター アプリをダウンロードして実行する
- 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()
に配置します。このフラグメントがアタッチされているアプリへの参照が必要です。これは、ビューモデル ファクトリ プロバイダに渡すためです。
値が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
内:
- 現在の Activity をバインディングのライフサイクル オーナーとして設定します。次のコードを
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 つが必要です。
- ジョブ
- ディスパッチャー
- スコープ
ジョブ: 基本的に、ジョブはキャンセル可能なものです。すべてのコルーチンにはジョブがあり、ジョブを使用してコルーチンをキャンセルできます。ジョブは親子階層に配置できます。親ジョブをキャンセルすると、ジョブのすべての子が直ちにキャンセルされます。これは、各コルーチンを手動でキャンセルするよりもはるかに便利です。
ディスパッチャ: ディスパッチャは、さまざまなスレッドで実行するコルーチンを送信します。たとえば、Dispatcher.Main
はメインスレッドでタスクを実行し、Dispatcher.IO
はブロッキング I/O タスクをスレッドの共有プールにオフロードします。
スコープ: コルーチンのスコープは、コルーチンが実行されるコンテキストを定義します。スコープは、コルーチンのジョブとディスパッチャに関する情報を組み合わせたものです。スコープはコルーチンを追跡します。コルーチンを起動すると、コルーチンは「スコープ内」になります。つまり、どのスコープがコルーチンを追跡するかを指定したことになります。
ユーザーが睡眠データを次の方法で操作できるようにします。
- ユーザーが [開始] ボタンをタップすると、アプリは新しい睡眠夜を作成し、データベースに保存します。
- ユーザーが [停止] ボタンをタップすると、アプリは終了時刻で夜を更新します。
- ユーザーが [クリア] ボタンをタップすると、アプリはデータベース内のデータを消去します。
これらのデータベース操作は時間がかかる可能性があるため、別のスレッドで実行する必要があります。
ステップ 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
がない場合は、nullableSleepNight
を返すprivate suspend
関数として定義します。関数は何かを返す必要があるため、このままではエラーが残ります。
private suspend fun getTonightFromDatabase(): SleepNight? { }
-
getTonightFromDatabase()
の関数本体内で、Dispatchers.IO
コンテキストで実行されるコルーチンの結果を返します。データベースからデータを取得することは I/O オペレーションであり、UI とは関係がないため、I/O ディスパッチャを使用します。
return withContext(Dispatchers.IO) {}
- 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()
と非常によく似ています。
- まず、
onStartTracking()
の関数定義から始めます。クリック ハンドラは、SleepTrackerViewModel
ファイルのonCleared()
の上に配置できます。
fun onStartTracking() {}
onStartTracking()
内で、uiScope
でコルーチンを起動します。これは、この結果を使用して UI を更新する必要があるためです。
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()}"
- アプリをビルドして実行します。[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
マップを使用します。
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()
関数を使用します。Transformations
クラスのmap()
関数にnights
を渡します。文字列リソースにアクセスするには、マッピング関数をformatNights()
を呼び出すように定義します。nights
とResources
オブジェクトを指定します。
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
fragment_sleep_tracker.xml
レイアウト ファイルを開きます。TextView
のandroid:text
プロパティで、リソース文字列をnightsString
への参照に置き換えることができるようになりました。
"@{sleepTrackerViewModel.nightsString}"
- コードを再ビルドしてアプリを実行します。開始時刻を含むすべての睡眠データが表示されるはずです。
- [開始] ボタンを数回タップすると、さらに多くのデータが表示されます。
次のステップでは、[停止] ボタンの機能を有効にします。
ステップ 4: [Stop] ボタンのクリック ハンドラを追加する
前の手順と同じパターンを使用して、SleepTrackerViewModel.
の [Stop] ボタンのクリック ハンドラを実装します。
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: [Clear] ボタンのクリック ハンドラを追加する
- 同様に、
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 スレッドがブロックされないように、suspend 関数を呼び出して長時間実行される処理を行います。
- 長時間実行される作業は 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 のランディング ページをご覧ください。