From 8ba55c6819c8b02e7a7fa926be09ec2ccb4535ef Mon Sep 17 00:00:00 2001 From: Jhansi <24260141+jtavva@users.noreply.github.com> Date: Wed, 26 Aug 2020 12:27:17 -0700 Subject: [PATCH] Use ViewmodelScope instead of creating coroutine scope * Use viewModelScope to call the database operations. * Remove Dispatcher.IO context and coroutine scope for @Dao methods. Room library automatically generated the code to make database calls in a background thread, so you don't explicitly need to move them to a background thread. A suspend @Dao method and a `@Query` method returning LiveData in Room automatically uses a background thread for database calls. You don't have to explicitly specify the Dispatcher.IO. Room does it for you in generated implementation. --- DevBytesRepository/app/build.gradle | 5 +- .../viewmodels/DevByteViewModel.kt | 27 +------- DevBytesWorkManager/app/build.gradle | 9 +-- .../viewmodels/DevByteViewModel.kt | 25 +------ RecyclerViewClickHandler/app/build.gradle | 4 ++ .../database/SleepDatabaseDao.kt | 10 +-- .../sleepdetail/SleepDetailFragment.kt | 2 +- .../sleepdetail/SleepDetailViewModel.kt | 17 ----- .../sleepquality/SleepQualityFragment.kt | 2 +- .../sleepquality/SleepQualityViewModel.kt | 46 ++----------- .../sleeptracker/SleepTrackerFragment.kt | 6 +- .../sleeptracker/SleepTrackerViewModel.kt | 65 ++++-------------- RecyclerViewClickHandler/build.gradle | 2 +- .../app/build.gradle | 4 ++ .../database/SleepDatabaseDao.kt | 10 +-- .../sleepquality/SleepQualityFragment.kt | 2 +- .../sleepquality/SleepQualityViewModel.kt | 47 ++----------- .../sleeptracker/SleepTrackerFragment.kt | 6 +- .../sleeptracker/SleepTrackerViewModel.kt | 66 ++++--------------- RecyclerViewDiffUtilDataBinding/build.gradle | 2 +- RecyclerViewFundamentals/app/build.gradle | 4 ++ .../database/SleepDatabaseDao.kt | 10 +-- .../sleepquality/SleepQualityFragment.kt | 2 +- .../sleepquality/SleepQualityViewModel.kt | 47 ++----------- .../sleeptracker/SleepTrackerFragment.kt | 6 +- .../sleeptracker/SleepTrackerViewModel.kt | 54 ++------------- RecyclerViewFundamentals/build.gradle | 2 +- RecyclerViewGridLayout/app/build.gradle | 4 ++ .../database/SleepDatabaseDao.kt | 10 +-- .../sleepquality/SleepQualityFragment.kt | 2 +- .../sleepquality/SleepQualityViewModel.kt | 47 ++----------- .../sleeptracker/SleepTrackerFragment.kt | 6 +- .../sleeptracker/SleepTrackerViewModel.kt | 66 ++++--------------- .../app/build.gradle | 4 ++ .../database/SleepDatabaseDao.kt | 10 +-- .../sleeptracker/SleepTrackerViewModel.kt | 26 ++------ TrackMySleepQualityFinal/app/build.gradle | 4 ++ .../database/SleepDatabaseDao.kt | 10 +-- .../sleepquality/SleepQualityViewModel.kt | 9 +-- .../sleeptracker/SleepTrackerViewModel.kt | 45 ++----------- .../app/build.gradle | 4 ++ 41 files changed, 165 insertions(+), 564 deletions(-) diff --git a/DevBytesRepository/app/build.gradle b/DevBytesRepository/app/build.gradle index 7f81cd5a4..fd0c3babc 100755 --- a/DevBytesRepository/app/build.gradle +++ b/DevBytesRepository/app/build.gradle @@ -28,8 +28,8 @@ android { targetSdkVersion 30 versionCode 1 versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true + multiDexEnabled true } buildTypes { release { @@ -53,7 +53,7 @@ dependencies { implementation 'com.google.android.material:material:1.0.0' // Android KTX - implementation 'androidx.core:core-ktx:1.0.2' + implementation 'androidx.core:core-ktx:1.3.1' // constraint layout implementation 'androidx.constraintlayout:constraintlayout:1.1.3' @@ -86,6 +86,7 @@ dependencies { // ViewModel and LiveData def lifecycle_version = "2.2.0" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" // logging implementation 'com.jakewharton.timber:timber:4.7.1' diff --git a/DevBytesRepository/app/src/main/java/com/example/android/devbyteviewer/viewmodels/DevByteViewModel.kt b/DevBytesRepository/app/src/main/java/com/example/android/devbyteviewer/viewmodels/DevByteViewModel.kt index 7135f007f..58b71e25e 100755 --- a/DevBytesRepository/app/src/main/java/com/example/android/devbyteviewer/viewmodels/DevByteViewModel.kt +++ b/DevBytesRepository/app/src/main/java/com/example/android/devbyteviewer/viewmodels/DevByteViewModel.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope import com.example.android.devbyteviewer.database.getDatabase import com.example.android.devbyteviewer.domain.DevByteVideo import com.example.android.devbyteviewer.network.DevByteNetwork @@ -42,7 +43,6 @@ import java.io.IOException */ class DevByteViewModel(application: Application) : AndroidViewModel(application) { - /** * The data source this ViewModel will fetch results from. */ @@ -53,21 +53,6 @@ class DevByteViewModel(application: Application) : AndroidViewModel(application) */ val playlist = videosRepository.videos - /** - * This is the job for all coroutines started by this ViewModel. - * - * Cancelling this job will cancel all coroutines started by this ViewModel. - */ - private val viewModelJob = SupervisorJob() - - /** - * This is the main scope for all coroutines launched by MainViewModel. - * - * Since we pass viewModelJob, you can cancel all coroutines launched by uiScope by calling - * viewModelJob.cancel() - */ - private val viewModelScope = CoroutineScope(viewModelJob + Dispatchers.Main) - /** * Event triggered for network error. This is private to avoid exposing a * way to set this value to observers. @@ -120,7 +105,6 @@ class DevByteViewModel(application: Application) : AndroidViewModel(application) } } - /** * Resets the network error flag. */ @@ -128,15 +112,6 @@ class DevByteViewModel(application: Application) : AndroidViewModel(application) _isNetworkErrorShown.value = true } - - /** - * Cancel all coroutines when the ViewModel is cleared - */ - override fun onCleared() { - super.onCleared() - viewModelJob.cancel() - } - /** * Factory for constructing DevByteViewModel with parameter */ diff --git a/DevBytesWorkManager/app/build.gradle b/DevBytesWorkManager/app/build.gradle index b9ea6fd9c..eb1a46df6 100755 --- a/DevBytesWorkManager/app/build.gradle +++ b/DevBytesWorkManager/app/build.gradle @@ -28,8 +28,8 @@ android { targetSdkVersion 30 versionCode 1 versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true + multiDexEnabled true } buildTypes { release { @@ -49,12 +49,12 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // support libraries - implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'com.google.android.material:material:1.0.0' // Android KTX - implementation 'androidx.core:core-ktx:1.0.2' + implementation 'androidx.core:core-ktx:1.3.1' // constraint layout implementation 'androidx.constraintlayout:constraintlayout:1.1.3' @@ -85,8 +85,9 @@ dependencies { // arch components // ViewModel and LiveData - def lifecycle_version = "2.2.0-alpha01" + def lifecycle_version = "2.2.0" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" // logging implementation 'com.jakewharton.timber:timber:4.7.1' diff --git a/DevBytesWorkManager/app/src/main/java/com/example/android/devbyteviewer/viewmodels/DevByteViewModel.kt b/DevBytesWorkManager/app/src/main/java/com/example/android/devbyteviewer/viewmodels/DevByteViewModel.kt index 7135f007f..47f2e3a98 100755 --- a/DevBytesWorkManager/app/src/main/java/com/example/android/devbyteviewer/viewmodels/DevByteViewModel.kt +++ b/DevBytesWorkManager/app/src/main/java/com/example/android/devbyteviewer/viewmodels/DevByteViewModel.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope import com.example.android.devbyteviewer.database.getDatabase import com.example.android.devbyteviewer.domain.DevByteVideo import com.example.android.devbyteviewer.network.DevByteNetwork @@ -53,21 +54,6 @@ class DevByteViewModel(application: Application) : AndroidViewModel(application) */ val playlist = videosRepository.videos - /** - * This is the job for all coroutines started by this ViewModel. - * - * Cancelling this job will cancel all coroutines started by this ViewModel. - */ - private val viewModelJob = SupervisorJob() - - /** - * This is the main scope for all coroutines launched by MainViewModel. - * - * Since we pass viewModelJob, you can cancel all coroutines launched by uiScope by calling - * viewModelJob.cancel() - */ - private val viewModelScope = CoroutineScope(viewModelJob + Dispatchers.Main) - /** * Event triggered for network error. This is private to avoid exposing a * way to set this value to observers. @@ -128,15 +114,6 @@ class DevByteViewModel(application: Application) : AndroidViewModel(application) _isNetworkErrorShown.value = true } - - /** - * Cancel all coroutines when the ViewModel is cleared - */ - override fun onCleared() { - super.onCleared() - viewModelJob.cancel() - } - /** * Factory for constructing DevByteViewModel with parameter */ diff --git a/RecyclerViewClickHandler/app/build.gradle b/RecyclerViewClickHandler/app/build.gradle index 5af118a08..4cc827737 100755 --- a/RecyclerViewClickHandler/app/build.gradle +++ b/RecyclerViewClickHandler/app/build.gradle @@ -62,6 +62,10 @@ dependencies { implementation 'androidx.legacy:legacy-support-v4:1.0.0' kapt "androidx.room:room-compiler:$room_version" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" + + // Kotlin Extensions and Coroutines support for Room + implementation "androidx.room:room-ktx:$room_version" // Coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" diff --git a/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt b/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt index 39c544418..31d9f646f 100755 --- a/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt +++ b/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt @@ -31,7 +31,7 @@ import androidx.room.Update interface SleepDatabaseDao { @Insert - fun insert(night: SleepNight) + suspend fun insert(night: SleepNight) /** * When updating a row with a value already set in a column, @@ -40,7 +40,7 @@ interface SleepDatabaseDao { * @param night new value to write */ @Update - fun update(night: SleepNight) + suspend fun update(night: SleepNight) /** * Selects and returns the row that matches the supplied start time, which is our key. @@ -48,7 +48,7 @@ interface SleepDatabaseDao { * @param key startTimeMilli to match */ @Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key") - fun get(key: Long): SleepNight + suspend fun get(key: Long): SleepNight /** * Deletes all values from the table. @@ -56,7 +56,7 @@ interface SleepDatabaseDao { * This does not delete the table, only its contents. */ @Query("DELETE FROM daily_sleep_quality_table") - fun clear() + suspend fun clear() /** * Selects and returns all rows in the table, @@ -70,7 +70,7 @@ interface SleepDatabaseDao { * Selects and returns the latest night. */ @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1") - fun getTonight(): SleepNight? + suspend fun getTonight(): SleepNight? /** * Selects and returns the night with given nightId. diff --git a/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepdetail/SleepDetailFragment.kt b/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepdetail/SleepDetailFragment.kt index 095b78712..d6d704301 100644 --- a/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepdetail/SleepDetailFragment.kt +++ b/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepdetail/SleepDetailFragment.kt @@ -67,7 +67,7 @@ class SleepDetailFragment : Fragment() { binding.setLifecycleOwner(this) // Add an Observer to the state variable for Navigating when a Quality icon is tapped. - sleepDetailViewModel.navigateToSleepTracker.observe(this, Observer { + sleepDetailViewModel.navigateToSleepTracker.observe(viewLifecycleOwner, Observer { if (it == true) { // Observed state is true. this.findNavController().navigate( SleepDetailFragmentDirections.actionSleepDetailFragmentToSleepTrackerFragment()) diff --git a/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepdetail/SleepDetailViewModel.kt b/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepdetail/SleepDetailViewModel.kt index e449a94e8..f2167408a 100644 --- a/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepdetail/SleepDetailViewModel.kt +++ b/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepdetail/SleepDetailViewModel.kt @@ -42,13 +42,6 @@ class SleepDetailViewModel( */ val database = dataSource - /** Coroutine setup variables */ - - /** - * viewModelJob allows us to cancel all coroutines started by this ViewModel. - */ - private val viewModelJob = Job() - private val night: LiveData fun getNight() = night @@ -72,16 +65,6 @@ class SleepDetailViewModel( val navigateToSleepTracker: LiveData get() = _navigateToSleepTracker - /** - * Cancels all coroutines when the ViewModel is cleared, to cleanup any pending work. - * - * onCleared() gets called when the ViewModel is destroyed. - */ - override fun onCleared() { - super.onCleared() - viewModelJob.cancel() - } - /** * Call this immediately after navigating to [SleepTrackerFragment] diff --git a/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt b/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt index 8c6f5fb26..f009e0662 100755 --- a/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt +++ b/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt @@ -70,7 +70,7 @@ class SleepQualityFragment : Fragment() { binding.sleepQualityViewModel = sleepQualityViewModel // Add an Observer to the state variable for Navigating when a Quality icon is tapped. - sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer { + sleepQualityViewModel.navigateToSleepTracker.observe(viewLifecycleOwner, Observer { if (it == true) { // Observed state is true. this.findNavController().navigate( SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment()) diff --git a/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt b/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt index 52ce4d8c6..28dd99b20 100755 --- a/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt +++ b/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt @@ -19,12 +19,9 @@ package com.example.android.trackmysleepquality.sleepquality import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.example.android.trackmysleepquality.database.SleepDatabaseDao -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext /** * ViewModel for SleepQualityFragment. @@ -40,25 +37,6 @@ class SleepQualityViewModel( */ val database = dataSource - /** Coroutine setup variables */ - - /** - * viewModelJob allows us to cancel all coroutines started by this ViewModel. - */ - private val viewModelJob = Job() - - /** - * A [CoroutineScope] keeps track of all coroutines started by this ViewModel. - * - * Because we pass it [viewModelJob], any coroutine started in this scope can be cancelled - * by calling `viewModelJob.cancel()` - * - * By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is - * the main thread on Android. This is a sensible default because most coroutines started by - * a [ViewModel] update the UI after performing some processing. - */ - private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) - /** * Variable that tells the fragment whether it should navigate to [SleepTrackerFragment]. * @@ -73,16 +51,6 @@ class SleepQualityViewModel( val navigateToSleepTracker: LiveData get() = _navigateToSleepTracker - /** - * Cancels all coroutines when the ViewModel is cleared, to cleanup any pending work. - * - * onCleared() gets called when the ViewModel is destroyed. - */ - override fun onCleared() { - super.onCleared() - viewModelJob.cancel() - } - /** * Call this immediately after navigating to [SleepTrackerFragment] */ @@ -96,14 +64,10 @@ class SleepQualityViewModel( * Then navigates back to the SleepTrackerFragment. */ fun onSetSleepQuality(quality: Int) { - uiScope.launch { - // IO is a thread pool for running operations that access the disk, such as - // our Room database. - withContext(Dispatchers.IO) { - val tonight = database.get(sleepNightKey) - tonight.sleepQuality = quality - database.update(tonight) - } + viewModelScope.launch { + val tonight = database.get(sleepNightKey) + tonight.sleepQuality = quality + database.update(tonight) // Setting this state variable to true will alert the observer and trigger navigation. _navigateToSleepTracker.value = true diff --git a/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt b/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt index 82d893f2e..c7885f558 100755 --- a/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt +++ b/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt @@ -89,10 +89,10 @@ class SleepTrackerFragment : Fragment() { // Add an Observer on the state variable for showing a Snackbar message // when the CLEAR button is pressed. - sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer { + sleepTrackerViewModel.showSnackBarEvent.observe(viewLifecycleOwner, Observer { if (it == true) { // Observed state is true. Snackbar.make( - activity!!.findViewById(android.R.id.content), + requireActivity().findViewById(android.R.id.content), getString(R.string.cleared_message), Snackbar.LENGTH_SHORT // How long to display the message. ).show() @@ -103,7 +103,7 @@ class SleepTrackerFragment : Fragment() { }) // Add an Observer on the state variable for Navigating when STOP button is pressed. - sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer { night -> + sleepTrackerViewModel.navigateToSleepQuality.observe(viewLifecycleOwner, Observer { night -> night?.let { // We need to get the navController from this, because button is not ready, and it // just has to be a view. For some reason, this only matters if we hit stop again diff --git a/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt b/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt index a42e69f04..2db7f7f6a 100755 --- a/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt +++ b/RecyclerViewClickHandler/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt @@ -21,14 +21,11 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.example.android.trackmysleepquality.database.SleepDatabaseDao import com.example.android.trackmysleepquality.database.SleepNight import com.example.android.trackmysleepquality.formatNights -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext /** * ViewModel for SleepTrackerFragment. @@ -42,25 +39,6 @@ class SleepTrackerViewModel( */ val database = dataSource - /** Coroutine variables */ - - /** - * viewModelJob allows us to cancel all coroutines started by this ViewModel. - */ - private var viewModelJob = Job() - - /** - * A [CoroutineScope] keeps track of all coroutines started by this ViewModel. - * - * Because we pass it [viewModelJob], any coroutine started in this uiScope can be cancelled - * by calling `viewModelJob.cancel()` - * - * By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is - * the main thread on Android. This is a sensible default because most coroutines started by - * a [ViewModel] update the UI after performing some processing. - */ - private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) - private var tonight = MutableLiveData() val nights = database.getAllNights() @@ -159,7 +137,7 @@ class SleepTrackerViewModel( } private fun initializeTonight() { - uiScope.launch { + viewModelScope.launch { tonight.value = getTonightFromDatabase() } } @@ -172,38 +150,30 @@ class SleepTrackerViewModel( * recording. */ private suspend fun getTonightFromDatabase(): SleepNight? { - return withContext(Dispatchers.IO) { - var night = database.getTonight() - if (night?.endTimeMilli != night?.startTimeMilli) { - night = null - } - night + var night = database.getTonight() + if (night?.endTimeMilli != night?.startTimeMilli) { + night = null } + return night } private suspend fun insert(night: SleepNight) { - withContext(Dispatchers.IO) { - database.insert(night) - } + database.insert(night) } private suspend fun update(night: SleepNight) { - withContext(Dispatchers.IO) { - database.update(night) - } + database.update(night) } private suspend fun clear() { - withContext(Dispatchers.IO) { - database.clear() - } + database.clear() } /** * Executes when the START button is clicked. */ fun onStart() { - uiScope.launch { + viewModelScope.launch { // Create a new night, which captures the current time, // and insert it into the database. val newNight = SleepNight() @@ -218,7 +188,7 @@ class SleepTrackerViewModel( * Executes when the STOP button is clicked. */ fun onStop() { - uiScope.launch { + viewModelScope.launch { // In Kotlin, the return@label syntax is used for specifying which function among // several nested ones this statement returns from. // In this case, we are specifying to return from launch(). @@ -238,7 +208,7 @@ class SleepTrackerViewModel( * Executes when the CLEAR button is clicked. */ fun onClear() { - uiScope.launch { + viewModelScope.launch { // Clear the database table. clear() @@ -249,15 +219,4 @@ class SleepTrackerViewModel( _showSnackbarEvent.value = true } } - - /** - * Called when the ViewModel is dismantled. - * At this point, we want to cancel all coroutines; - * otherwise we end up with processes that have nowhere to return to - * using memory and resources. - */ - override fun onCleared() { - super.onCleared() - viewModelJob.cancel() - } } \ No newline at end of file diff --git a/RecyclerViewClickHandler/build.gradle b/RecyclerViewClickHandler/build.gradle index 7807300d8..ece308351 100755 --- a/RecyclerViewClickHandler/build.gradle +++ b/RecyclerViewClickHandler/build.gradle @@ -21,7 +21,7 @@ buildscript { ext { kotlin_version = '1.3.72' archLifecycleVersion = '1.1.1' - room_version = '2.2.0' + room_version = '2.2.5' coroutine_version = '1.3.7' gradleVersion = '4.0.1' navigationVersion = '1.0.0-alpha07' diff --git a/RecyclerViewDiffUtilDataBinding/app/build.gradle b/RecyclerViewDiffUtilDataBinding/app/build.gradle index 5af118a08..4cc827737 100755 --- a/RecyclerViewDiffUtilDataBinding/app/build.gradle +++ b/RecyclerViewDiffUtilDataBinding/app/build.gradle @@ -62,6 +62,10 @@ dependencies { implementation 'androidx.legacy:legacy-support-v4:1.0.0' kapt "androidx.room:room-compiler:$room_version" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" + + // Kotlin Extensions and Coroutines support for Room + implementation "androidx.room:room-ktx:$room_version" // Coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" diff --git a/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt b/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt index 49f3ea106..ba566290a 100755 --- a/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt +++ b/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt @@ -31,7 +31,7 @@ import androidx.room.Update interface SleepDatabaseDao { @Insert - fun insert(night: SleepNight) + suspend fun insert(night: SleepNight) /** * When updating a row with a value already set in a column, @@ -40,7 +40,7 @@ interface SleepDatabaseDao { * @param night new value to write */ @Update - fun update(night: SleepNight) + suspend fun update(night: SleepNight) /** * Selects and returns the row that matches the supplied start time, which is our key. @@ -48,7 +48,7 @@ interface SleepDatabaseDao { * @param key startTimeMilli to match */ @Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key") - fun get(key: Long): SleepNight + suspend fun get(key: Long): SleepNight /** * Deletes all values from the table. @@ -56,7 +56,7 @@ interface SleepDatabaseDao { * This does not delete the table, only its contents. */ @Query("DELETE FROM daily_sleep_quality_table") - fun clear() + suspend fun clear() /** * Selects and returns all rows in the table, @@ -70,5 +70,5 @@ interface SleepDatabaseDao { * Selects and returns the latest night. */ @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1") - fun getTonight(): SleepNight? + suspend fun getTonight(): SleepNight? } diff --git a/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt b/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt index 8c6f5fb26..f009e0662 100755 --- a/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt +++ b/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt @@ -70,7 +70,7 @@ class SleepQualityFragment : Fragment() { binding.sleepQualityViewModel = sleepQualityViewModel // Add an Observer to the state variable for Navigating when a Quality icon is tapped. - sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer { + sleepQualityViewModel.navigateToSleepTracker.observe(viewLifecycleOwner, Observer { if (it == true) { // Observed state is true. this.findNavController().navigate( SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment()) diff --git a/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt b/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt index 31de35ee8..28dd99b20 100755 --- a/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt +++ b/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt @@ -19,12 +19,9 @@ package com.example.android.trackmysleepquality.sleepquality import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.example.android.trackmysleepquality.database.SleepDatabaseDao -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext /** * ViewModel for SleepQualityFragment. @@ -40,25 +37,6 @@ class SleepQualityViewModel( */ val database = dataSource - /** Coroutine setup variables */ - - /** - * viewModelJob allows us to cancel all coroutines started by this ViewModel. - */ - private val viewModelJob = Job() - - /** - * A [CoroutineScope] keeps track of all coroutines started by this ViewModel. - * - * Because we pass it [viewModelJob], any coroutine started in this scope can be cancelled - * by calling `viewModelJob.cancel()` - * - * By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is - * the main thread on Android. This is a sensible default because most coroutines started by - * a [ViewModel] update the UI after performing some processing. - */ - private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) - /** * Variable that tells the fragment whether it should navigate to [SleepTrackerFragment]. * @@ -73,17 +51,6 @@ class SleepQualityViewModel( val navigateToSleepTracker: LiveData get() = _navigateToSleepTracker - /** - * Cancels all coroutines when the ViewModel is cleared, to cleanup any pending work. - * - * onCleared() gets called when the ViewModel is destroyed. - */ - override fun onCleared() { - super.onCleared() - viewModelJob.cancel() - } - - /** * Call this immediately after navigating to [SleepTrackerFragment] */ @@ -97,14 +64,10 @@ class SleepQualityViewModel( * Then navigates back to the SleepTrackerFragment. */ fun onSetSleepQuality(quality: Int) { - uiScope.launch { - // IO is a thread pool for running operations that access the disk, such as - // our Room database. - withContext(Dispatchers.IO) { - val tonight = database.get(sleepNightKey) - tonight.sleepQuality = quality - database.update(tonight) - } + viewModelScope.launch { + val tonight = database.get(sleepNightKey) + tonight.sleepQuality = quality + database.update(tonight) // Setting this state variable to true will alert the observer and trigger navigation. _navigateToSleepTracker.value = true diff --git a/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt b/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt index 9c486f220..4d958d7ae 100755 --- a/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt +++ b/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt @@ -84,10 +84,10 @@ class SleepTrackerFragment : Fragment() { // Add an Observer on the state variable for showing a Snackbar message // when the CLEAR button is pressed. - sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer { + sleepTrackerViewModel.showSnackBarEvent.observe(viewLifecycleOwner, Observer { if (it == true) { // Observed state is true. Snackbar.make( - activity!!.findViewById(android.R.id.content), + requireActivity().findViewById(android.R.id.content), getString(R.string.cleared_message), Snackbar.LENGTH_SHORT // How long to display the message. ).show() @@ -98,7 +98,7 @@ class SleepTrackerFragment : Fragment() { }) // Add an Observer on the state variable for Navigating when STOP button is pressed. - sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer { night -> + sleepTrackerViewModel.navigateToSleepQuality.observe(viewLifecycleOwner, Observer { night -> night?.let { // We need to get the navController from this, because button is not ready, and it // just has to be a view. For some reason, this only matters if we hit stop again diff --git a/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt b/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt index 4bc42992d..3fcbebadd 100755 --- a/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt +++ b/RecyclerViewDiffUtilDataBinding/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt @@ -21,14 +21,11 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.example.android.trackmysleepquality.database.SleepDatabaseDao import com.example.android.trackmysleepquality.database.SleepNight import com.example.android.trackmysleepquality.formatNights -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext /** * ViewModel for SleepTrackerFragment. @@ -42,25 +39,6 @@ class SleepTrackerViewModel( */ val database = dataSource - /** Coroutine variables */ - - /** - * viewModelJob allows us to cancel all coroutines started by this ViewModel. - */ - private var viewModelJob = Job() - - /** - * A [CoroutineScope] keeps track of all coroutines started by this ViewModel. - * - * Because we pass it [viewModelJob], any coroutine started in this uiScope can be cancelled - * by calling `viewModelJob.cancel()` - * - * By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is - * the main thread on Android. This is a sensible default because most coroutines started by - * a [ViewModel] update the UI after performing some processing. - */ - private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) - private var tonight = MutableLiveData() val nights = database.getAllNights() @@ -93,7 +71,6 @@ class SleepTrackerViewModel( it?.isNotEmpty() } - /** * Request a toast by setting this value to true. * @@ -145,7 +122,7 @@ class SleepTrackerViewModel( } private fun initializeTonight() { - uiScope.launch { + viewModelScope.launch { tonight.value = getTonightFromDatabase() } } @@ -158,38 +135,30 @@ class SleepTrackerViewModel( * recording. */ private suspend fun getTonightFromDatabase(): SleepNight? { - return withContext(Dispatchers.IO) { - var night = database.getTonight() - if (night?.endTimeMilli != night?.startTimeMilli) { - night = null - } - night + var night = database.getTonight() + if (night?.endTimeMilli != night?.startTimeMilli) { + night = null } + return night } private suspend fun insert(night: SleepNight) { - withContext(Dispatchers.IO) { - database.insert(night) - } + database.insert(night) } private suspend fun update(night: SleepNight) { - withContext(Dispatchers.IO) { - database.update(night) - } + database.update(night) } private suspend fun clear() { - withContext(Dispatchers.IO) { - database.clear() - } + database.clear() } /** * Executes when the START button is clicked. */ fun onStart() { - uiScope.launch { + viewModelScope.launch { // Create a new night, which captures the current time, // and insert it into the database. val newNight = SleepNight() @@ -204,7 +173,7 @@ class SleepTrackerViewModel( * Executes when the STOP button is clicked. */ fun onStop() { - uiScope.launch { + viewModelScope.launch { // In Kotlin, the return@label syntax is used for specifying which function among // several nested ones this statement returns from. // In this case, we are specifying to return from launch(). @@ -224,7 +193,7 @@ class SleepTrackerViewModel( * Executes when the CLEAR button is clicked. */ fun onClear() { - uiScope.launch { + viewModelScope.launch { // Clear the database table. clear() @@ -235,15 +204,4 @@ class SleepTrackerViewModel( _showSnackbarEvent.value = true } } - - /** - * Called when the ViewModel is dismantled. - * At this point, we want to cancel all coroutines; - * otherwise we end up with processes that have nowhere to return to - * using memory and resources. - */ - override fun onCleared() { - super.onCleared() - viewModelJob.cancel() - } } \ No newline at end of file diff --git a/RecyclerViewDiffUtilDataBinding/build.gradle b/RecyclerViewDiffUtilDataBinding/build.gradle index c221dc8bb..ece308351 100755 --- a/RecyclerViewDiffUtilDataBinding/build.gradle +++ b/RecyclerViewDiffUtilDataBinding/build.gradle @@ -21,7 +21,7 @@ buildscript { ext { kotlin_version = '1.3.72' archLifecycleVersion = '1.1.1' - room_version = '2.0.0' + room_version = '2.2.5' coroutine_version = '1.3.7' gradleVersion = '4.0.1' navigationVersion = '1.0.0-alpha07' diff --git a/RecyclerViewFundamentals/app/build.gradle b/RecyclerViewFundamentals/app/build.gradle index 3a23b8ae8..94263dcdb 100755 --- a/RecyclerViewFundamentals/app/build.gradle +++ b/RecyclerViewFundamentals/app/build.gradle @@ -62,6 +62,10 @@ dependencies { implementation 'androidx.legacy:legacy-support-v4:1.0.0' kapt "androidx.room:room-compiler:$room_version" implementation "androidx.lifecycle:lifecycle-extensions:2.0.0" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" + + // Kotlin Extensions and Coroutines support for Room + implementation "androidx.room:room-ktx:$room_version" // Coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" diff --git a/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt b/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt index 49f3ea106..ba566290a 100755 --- a/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt +++ b/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt @@ -31,7 +31,7 @@ import androidx.room.Update interface SleepDatabaseDao { @Insert - fun insert(night: SleepNight) + suspend fun insert(night: SleepNight) /** * When updating a row with a value already set in a column, @@ -40,7 +40,7 @@ interface SleepDatabaseDao { * @param night new value to write */ @Update - fun update(night: SleepNight) + suspend fun update(night: SleepNight) /** * Selects and returns the row that matches the supplied start time, which is our key. @@ -48,7 +48,7 @@ interface SleepDatabaseDao { * @param key startTimeMilli to match */ @Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key") - fun get(key: Long): SleepNight + suspend fun get(key: Long): SleepNight /** * Deletes all values from the table. @@ -56,7 +56,7 @@ interface SleepDatabaseDao { * This does not delete the table, only its contents. */ @Query("DELETE FROM daily_sleep_quality_table") - fun clear() + suspend fun clear() /** * Selects and returns all rows in the table, @@ -70,5 +70,5 @@ interface SleepDatabaseDao { * Selects and returns the latest night. */ @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1") - fun getTonight(): SleepNight? + suspend fun getTonight(): SleepNight? } diff --git a/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt b/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt index 8c6f5fb26..f009e0662 100755 --- a/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt +++ b/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt @@ -70,7 +70,7 @@ class SleepQualityFragment : Fragment() { binding.sleepQualityViewModel = sleepQualityViewModel // Add an Observer to the state variable for Navigating when a Quality icon is tapped. - sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer { + sleepQualityViewModel.navigateToSleepTracker.observe(viewLifecycleOwner, Observer { if (it == true) { // Observed state is true. this.findNavController().navigate( SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment()) diff --git a/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt b/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt index 31de35ee8..28dd99b20 100755 --- a/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt +++ b/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt @@ -19,12 +19,9 @@ package com.example.android.trackmysleepquality.sleepquality import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.example.android.trackmysleepquality.database.SleepDatabaseDao -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext /** * ViewModel for SleepQualityFragment. @@ -40,25 +37,6 @@ class SleepQualityViewModel( */ val database = dataSource - /** Coroutine setup variables */ - - /** - * viewModelJob allows us to cancel all coroutines started by this ViewModel. - */ - private val viewModelJob = Job() - - /** - * A [CoroutineScope] keeps track of all coroutines started by this ViewModel. - * - * Because we pass it [viewModelJob], any coroutine started in this scope can be cancelled - * by calling `viewModelJob.cancel()` - * - * By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is - * the main thread on Android. This is a sensible default because most coroutines started by - * a [ViewModel] update the UI after performing some processing. - */ - private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) - /** * Variable that tells the fragment whether it should navigate to [SleepTrackerFragment]. * @@ -73,17 +51,6 @@ class SleepQualityViewModel( val navigateToSleepTracker: LiveData get() = _navigateToSleepTracker - /** - * Cancels all coroutines when the ViewModel is cleared, to cleanup any pending work. - * - * onCleared() gets called when the ViewModel is destroyed. - */ - override fun onCleared() { - super.onCleared() - viewModelJob.cancel() - } - - /** * Call this immediately after navigating to [SleepTrackerFragment] */ @@ -97,14 +64,10 @@ class SleepQualityViewModel( * Then navigates back to the SleepTrackerFragment. */ fun onSetSleepQuality(quality: Int) { - uiScope.launch { - // IO is a thread pool for running operations that access the disk, such as - // our Room database. - withContext(Dispatchers.IO) { - val tonight = database.get(sleepNightKey) - tonight.sleepQuality = quality - database.update(tonight) - } + viewModelScope.launch { + val tonight = database.get(sleepNightKey) + tonight.sleepQuality = quality + database.update(tonight) // Setting this state variable to true will alert the observer and trigger navigation. _navigateToSleepTracker.value = true diff --git a/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt b/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt index e6456dfc3..0af83a8a3 100755 --- a/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt +++ b/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt @@ -84,10 +84,10 @@ class SleepTrackerFragment : Fragment() { // Add an Observer on the state variable for showing a Snackbar message // when the CLEAR button is pressed. - sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer { + sleepTrackerViewModel.showSnackBarEvent.observe(viewLifecycleOwner, Observer { if (it == true) { // Observed state is true. Snackbar.make( - activity!!.findViewById(android.R.id.content), + requireActivity().findViewById(android.R.id.content), getString(R.string.cleared_message), Snackbar.LENGTH_SHORT // How long to display the message. ).show() @@ -98,7 +98,7 @@ class SleepTrackerFragment : Fragment() { }) // Add an Observer on the state variable for Navigating when STOP button is pressed. - sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer { night -> + sleepTrackerViewModel.navigateToSleepQuality.observe(viewLifecycleOwner, Observer { night -> night?.let { // We need to get the navController from this, because button is not ready, and it // just has to be a view. For some reason, this only matters if we hit stop again diff --git a/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt b/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt index 4bc42992d..dcfe44817 100755 --- a/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt +++ b/RecyclerViewFundamentals/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt @@ -21,14 +21,11 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.example.android.trackmysleepquality.database.SleepDatabaseDao import com.example.android.trackmysleepquality.database.SleepNight import com.example.android.trackmysleepquality.formatNights -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext /** * ViewModel for SleepTrackerFragment. @@ -42,24 +39,6 @@ class SleepTrackerViewModel( */ val database = dataSource - /** Coroutine variables */ - - /** - * viewModelJob allows us to cancel all coroutines started by this ViewModel. - */ - private var viewModelJob = Job() - - /** - * A [CoroutineScope] keeps track of all coroutines started by this ViewModel. - * - * Because we pass it [viewModelJob], any coroutine started in this uiScope can be cancelled - * by calling `viewModelJob.cancel()` - * - * By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is - * the main thread on Android. This is a sensible default because most coroutines started by - * a [ViewModel] update the UI after performing some processing. - */ - private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) private var tonight = MutableLiveData() @@ -145,51 +124,43 @@ class SleepTrackerViewModel( } private fun initializeTonight() { - uiScope.launch { + viewModelScope.launch { tonight.value = getTonightFromDatabase() } } /** * Handling the case of the stopped app or forgotten recording, - * the start and end times will be the same.j + * the start and end times will be the same. * * If the start time and end time are not the same, then we do not have an unfinished * recording. */ private suspend fun getTonightFromDatabase(): SleepNight? { - return withContext(Dispatchers.IO) { var night = database.getTonight() if (night?.endTimeMilli != night?.startTimeMilli) { night = null } - night - } + return night } private suspend fun insert(night: SleepNight) { - withContext(Dispatchers.IO) { database.insert(night) - } } private suspend fun update(night: SleepNight) { - withContext(Dispatchers.IO) { database.update(night) - } } private suspend fun clear() { - withContext(Dispatchers.IO) { database.clear() - } } /** * Executes when the START button is clicked. */ fun onStart() { - uiScope.launch { + viewModelScope.launch { // Create a new night, which captures the current time, // and insert it into the database. val newNight = SleepNight() @@ -204,7 +175,7 @@ class SleepTrackerViewModel( * Executes when the STOP button is clicked. */ fun onStop() { - uiScope.launch { + viewModelScope.launch { // In Kotlin, the return@label syntax is used for specifying which function among // several nested ones this statement returns from. // In this case, we are specifying to return from launch(). @@ -224,7 +195,7 @@ class SleepTrackerViewModel( * Executes when the CLEAR button is clicked. */ fun onClear() { - uiScope.launch { + viewModelScope.launch { // Clear the database table. clear() @@ -235,15 +206,4 @@ class SleepTrackerViewModel( _showSnackbarEvent.value = true } } - - /** - * Called when the ViewModel is dismantled. - * At this point, we want to cancel all coroutines; - * otherwise we end up with processes that have nowhere to return to - * using memory and resources. - */ - override fun onCleared() { - super.onCleared() - viewModelJob.cancel() - } } \ No newline at end of file diff --git a/RecyclerViewFundamentals/build.gradle b/RecyclerViewFundamentals/build.gradle index e7527911b..3fa751539 100755 --- a/RecyclerViewFundamentals/build.gradle +++ b/RecyclerViewFundamentals/build.gradle @@ -21,7 +21,7 @@ buildscript { ext { kotlin_version = '1.3.72' archLifecycleVersion = '1.1.1' - room_version = '2.0.0' + room_version = '2.2.5' coroutine_version = '1.3.7' gradleVersion = '4.0.1' navigationVersion = '1.0.0-alpha07' diff --git a/RecyclerViewGridLayout/app/build.gradle b/RecyclerViewGridLayout/app/build.gradle index bb8d2127f..260ccf3ad 100755 --- a/RecyclerViewGridLayout/app/build.gradle +++ b/RecyclerViewGridLayout/app/build.gradle @@ -63,6 +63,10 @@ dependencies { implementation 'androidx.legacy:legacy-support-v4:1.0.0' kapt "androidx.room:room-compiler:$room_version" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" + + // Kotlin Extensions and Coroutines support for Room + implementation "androidx.room:room-ktx:$room_version" // Coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" diff --git a/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt b/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt index 49f3ea106..ba566290a 100755 --- a/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt +++ b/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt @@ -31,7 +31,7 @@ import androidx.room.Update interface SleepDatabaseDao { @Insert - fun insert(night: SleepNight) + suspend fun insert(night: SleepNight) /** * When updating a row with a value already set in a column, @@ -40,7 +40,7 @@ interface SleepDatabaseDao { * @param night new value to write */ @Update - fun update(night: SleepNight) + suspend fun update(night: SleepNight) /** * Selects and returns the row that matches the supplied start time, which is our key. @@ -48,7 +48,7 @@ interface SleepDatabaseDao { * @param key startTimeMilli to match */ @Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key") - fun get(key: Long): SleepNight + suspend fun get(key: Long): SleepNight /** * Deletes all values from the table. @@ -56,7 +56,7 @@ interface SleepDatabaseDao { * This does not delete the table, only its contents. */ @Query("DELETE FROM daily_sleep_quality_table") - fun clear() + suspend fun clear() /** * Selects and returns all rows in the table, @@ -70,5 +70,5 @@ interface SleepDatabaseDao { * Selects and returns the latest night. */ @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1") - fun getTonight(): SleepNight? + suspend fun getTonight(): SleepNight? } diff --git a/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt b/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt index 8c6f5fb26..f009e0662 100755 --- a/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt +++ b/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt @@ -70,7 +70,7 @@ class SleepQualityFragment : Fragment() { binding.sleepQualityViewModel = sleepQualityViewModel // Add an Observer to the state variable for Navigating when a Quality icon is tapped. - sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer { + sleepQualityViewModel.navigateToSleepTracker.observe(viewLifecycleOwner, Observer { if (it == true) { // Observed state is true. this.findNavController().navigate( SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment()) diff --git a/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt b/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt index 31de35ee8..28dd99b20 100755 --- a/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt +++ b/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt @@ -19,12 +19,9 @@ package com.example.android.trackmysleepquality.sleepquality import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.example.android.trackmysleepquality.database.SleepDatabaseDao -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext /** * ViewModel for SleepQualityFragment. @@ -40,25 +37,6 @@ class SleepQualityViewModel( */ val database = dataSource - /** Coroutine setup variables */ - - /** - * viewModelJob allows us to cancel all coroutines started by this ViewModel. - */ - private val viewModelJob = Job() - - /** - * A [CoroutineScope] keeps track of all coroutines started by this ViewModel. - * - * Because we pass it [viewModelJob], any coroutine started in this scope can be cancelled - * by calling `viewModelJob.cancel()` - * - * By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is - * the main thread on Android. This is a sensible default because most coroutines started by - * a [ViewModel] update the UI after performing some processing. - */ - private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) - /** * Variable that tells the fragment whether it should navigate to [SleepTrackerFragment]. * @@ -73,17 +51,6 @@ class SleepQualityViewModel( val navigateToSleepTracker: LiveData get() = _navigateToSleepTracker - /** - * Cancels all coroutines when the ViewModel is cleared, to cleanup any pending work. - * - * onCleared() gets called when the ViewModel is destroyed. - */ - override fun onCleared() { - super.onCleared() - viewModelJob.cancel() - } - - /** * Call this immediately after navigating to [SleepTrackerFragment] */ @@ -97,14 +64,10 @@ class SleepQualityViewModel( * Then navigates back to the SleepTrackerFragment. */ fun onSetSleepQuality(quality: Int) { - uiScope.launch { - // IO is a thread pool for running operations that access the disk, such as - // our Room database. - withContext(Dispatchers.IO) { - val tonight = database.get(sleepNightKey) - tonight.sleepQuality = quality - database.update(tonight) - } + viewModelScope.launch { + val tonight = database.get(sleepNightKey) + tonight.sleepQuality = quality + database.update(tonight) // Setting this state variable to true will alert the observer and trigger navigation. _navigateToSleepTracker.value = true diff --git a/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt b/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt index c47869a20..d241ed61a 100755 --- a/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt +++ b/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt @@ -85,10 +85,10 @@ class SleepTrackerFragment : Fragment() { // Add an Observer on the state variable for showing a Snackbar message // when the CLEAR button is pressed. - sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer { + sleepTrackerViewModel.showSnackBarEvent.observe(viewLifecycleOwner, Observer { if (it == true) { // Observed state is true. Snackbar.make( - activity!!.findViewById(android.R.id.content), + requireActivity().findViewById(android.R.id.content), getString(R.string.cleared_message), Snackbar.LENGTH_SHORT // How long to display the message. ).show() @@ -99,7 +99,7 @@ class SleepTrackerFragment : Fragment() { }) // Add an Observer on the state variable for Navigating when STOP button is pressed. - sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer { night -> + sleepTrackerViewModel.navigateToSleepQuality.observe(viewLifecycleOwner, Observer { night -> night?.let { // We need to get the navController from this, because button is not ready, and it // just has to be a view. For some reason, this only matters if we hit stop again diff --git a/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt b/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt index 4bc42992d..3fcbebadd 100755 --- a/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt +++ b/RecyclerViewGridLayout/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt @@ -21,14 +21,11 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.example.android.trackmysleepquality.database.SleepDatabaseDao import com.example.android.trackmysleepquality.database.SleepNight import com.example.android.trackmysleepquality.formatNights -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext /** * ViewModel for SleepTrackerFragment. @@ -42,25 +39,6 @@ class SleepTrackerViewModel( */ val database = dataSource - /** Coroutine variables */ - - /** - * viewModelJob allows us to cancel all coroutines started by this ViewModel. - */ - private var viewModelJob = Job() - - /** - * A [CoroutineScope] keeps track of all coroutines started by this ViewModel. - * - * Because we pass it [viewModelJob], any coroutine started in this uiScope can be cancelled - * by calling `viewModelJob.cancel()` - * - * By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is - * the main thread on Android. This is a sensible default because most coroutines started by - * a [ViewModel] update the UI after performing some processing. - */ - private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) - private var tonight = MutableLiveData() val nights = database.getAllNights() @@ -93,7 +71,6 @@ class SleepTrackerViewModel( it?.isNotEmpty() } - /** * Request a toast by setting this value to true. * @@ -145,7 +122,7 @@ class SleepTrackerViewModel( } private fun initializeTonight() { - uiScope.launch { + viewModelScope.launch { tonight.value = getTonightFromDatabase() } } @@ -158,38 +135,30 @@ class SleepTrackerViewModel( * recording. */ private suspend fun getTonightFromDatabase(): SleepNight? { - return withContext(Dispatchers.IO) { - var night = database.getTonight() - if (night?.endTimeMilli != night?.startTimeMilli) { - night = null - } - night + var night = database.getTonight() + if (night?.endTimeMilli != night?.startTimeMilli) { + night = null } + return night } private suspend fun insert(night: SleepNight) { - withContext(Dispatchers.IO) { - database.insert(night) - } + database.insert(night) } private suspend fun update(night: SleepNight) { - withContext(Dispatchers.IO) { - database.update(night) - } + database.update(night) } private suspend fun clear() { - withContext(Dispatchers.IO) { - database.clear() - } + database.clear() } /** * Executes when the START button is clicked. */ fun onStart() { - uiScope.launch { + viewModelScope.launch { // Create a new night, which captures the current time, // and insert it into the database. val newNight = SleepNight() @@ -204,7 +173,7 @@ class SleepTrackerViewModel( * Executes when the STOP button is clicked. */ fun onStop() { - uiScope.launch { + viewModelScope.launch { // In Kotlin, the return@label syntax is used for specifying which function among // several nested ones this statement returns from. // In this case, we are specifying to return from launch(). @@ -224,7 +193,7 @@ class SleepTrackerViewModel( * Executes when the CLEAR button is clicked. */ fun onClear() { - uiScope.launch { + viewModelScope.launch { // Clear the database table. clear() @@ -235,15 +204,4 @@ class SleepTrackerViewModel( _showSnackbarEvent.value = true } } - - /** - * Called when the ViewModel is dismantled. - * At this point, we want to cancel all coroutines; - * otherwise we end up with processes that have nowhere to return to - * using memory and resources. - */ - override fun onCleared() { - super.onCleared() - viewModelJob.cancel() - } } \ No newline at end of file diff --git a/TrackMySleepQualityCoroutines/app/build.gradle b/TrackMySleepQualityCoroutines/app/build.gradle index 12ca5e170..dcd46fa64 100755 --- a/TrackMySleepQualityCoroutines/app/build.gradle +++ b/TrackMySleepQualityCoroutines/app/build.gradle @@ -62,6 +62,10 @@ dependencies { implementation 'androidx.legacy:legacy-support-v4:1.0.0' kapt "androidx.room:room-compiler:$room_version" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" + + // Kotlin Extensions and Coroutines support for Room + implementation "androidx.room:room-ktx:$room_version" // Coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" diff --git a/TrackMySleepQualityCoroutines/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt b/TrackMySleepQualityCoroutines/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt index 639600116..96a0945b2 100755 --- a/TrackMySleepQualityCoroutines/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt +++ b/TrackMySleepQualityCoroutines/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt @@ -26,19 +26,19 @@ import androidx.room.Update interface SleepDatabaseDao { @Insert - fun insert(night: SleepNight) + suspend fun insert(night: SleepNight) @Update - fun update(night: SleepNight) + suspend fun update(night: SleepNight) @Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key") - fun get(key: Long): SleepNight? + suspend fun get(key: Long): SleepNight? @Query("DELETE FROM daily_sleep_quality_table") - fun clear() + suspend fun clear() @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1") - fun getTonight(): SleepNight? + suspend fun getTonight(): SleepNight? @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC") fun getAllNights(): LiveData> diff --git a/TrackMySleepQualityCoroutines/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt b/TrackMySleepQualityCoroutines/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt index 8f17ebcd3..7427811fd 100755 --- a/TrackMySleepQualityCoroutines/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt +++ b/TrackMySleepQualityCoroutines/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt @@ -20,6 +20,7 @@ import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations +import androidx.lifecycle.viewModelScope import com.example.android.trackmysleepquality.database.SleepDatabaseDao import com.example.android.trackmysleepquality.database.SleepNight import com.example.android.trackmysleepquality.formatNights @@ -36,11 +37,6 @@ class SleepTrackerViewModel( val database: SleepDatabaseDao, application: Application) : AndroidViewModel(application) { - private var viewModelJob = Job() - - - private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) - private val nights = database.getAllNights() val nightsString = Transformations.map(nights) { nights -> @@ -54,25 +50,22 @@ class SleepTrackerViewModel( } private fun initializeTonight() { - uiScope.launch { + viewModelScope.launch { tonight.value = getTonightFromDatabase() } } private suspend fun getTonightFromDatabase(): SleepNight? { - return withContext(Dispatchers.IO) { - var night = database.getTonight() if (night?.endTimeMilli != night?.startTimeMilli) { night = null } - night - } + return night } fun onStartTracking() { - uiScope.launch { + viewModelScope.launch { val newNight = SleepNight() insert(newNight) tonight.value = getTonightFromDatabase() @@ -80,13 +73,11 @@ class SleepTrackerViewModel( } private suspend fun insert(night: SleepNight) { - withContext(Dispatchers.IO) { database.insert(night) - } } fun onStopTracking() { - uiScope.launch { + viewModelScope.launch { val oldNight = tonight.value ?: return@launch oldNight.endTimeMilli = System.currentTimeMillis() update(oldNight) @@ -100,7 +91,7 @@ class SleepTrackerViewModel( } fun onClear() { - uiScope.launch { + viewModelScope.launch { clear() tonight.value = null } @@ -111,10 +102,5 @@ class SleepTrackerViewModel( database.clear() } } - - override fun onCleared() { - super.onCleared() - viewModelJob.cancel() - } } diff --git a/TrackMySleepQualityFinal/app/build.gradle b/TrackMySleepQualityFinal/app/build.gradle index ad16d50d1..8c9d9e1aa 100755 --- a/TrackMySleepQualityFinal/app/build.gradle +++ b/TrackMySleepQualityFinal/app/build.gradle @@ -61,6 +61,10 @@ dependencies { implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" + + // Kotlin Extensions and Coroutines support for Room + implementation "androidx.room:room-ktx:$room_version" // Coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" diff --git a/TrackMySleepQualityFinal/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt b/TrackMySleepQualityFinal/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt index d5a4a437a..8f07c0831 100755 --- a/TrackMySleepQualityFinal/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt +++ b/TrackMySleepQualityFinal/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt @@ -31,7 +31,7 @@ import androidx.room.Update interface SleepDatabaseDao { @Insert - fun insert(night: SleepNight) + suspend fun insert(night: SleepNight) /** * When updating a row with a value already set in a column, @@ -40,7 +40,7 @@ interface SleepDatabaseDao { * @param night new value to write */ @Update - fun update(night: SleepNight) + suspend fun update(night: SleepNight) /** * Selects and returns the row that matches the supplied start time, which is our key. @@ -48,14 +48,14 @@ interface SleepDatabaseDao { * @param key startTimeMilli to match */ @Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key") - fun get(key: Long): SleepNight? + suspend fun get(key: Long): SleepNight? /** * Deletes all values from the table. * * This does not delete the table, only its contents. */ @Query("DELETE FROM daily_sleep_quality_table") - fun clear() + suspend fun clear() /** * Selects and returns all rows in the table, @@ -69,5 +69,5 @@ interface SleepDatabaseDao { * Selects and returns the latest night. */ @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1") - fun getTonight(): SleepNight? + suspend fun getTonight(): SleepNight? } diff --git a/TrackMySleepQualityFinal/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt b/TrackMySleepQualityFinal/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt index 783ce9743..ad1127a75 100755 --- a/TrackMySleepQualityFinal/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt +++ b/TrackMySleepQualityFinal/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt @@ -19,6 +19,7 @@ package com.example.android.trackmysleepquality.sleepquality import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.example.android.trackmysleepquality.database.SleepDatabaseDao import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -92,14 +93,10 @@ class SleepQualityViewModel( * Then navigates back to the SleepTrackerFragment. */ fun onSetSleepQuality(quality: Int) { - uiScope.launch { - // IO is a thread pool for running operations that access the disk, such as - // our Room database. - withContext(Dispatchers.IO) { - val tonight = database.get(sleepNightKey) ?: return@withContext + viewModelScope.launch { + val tonight = database.get(sleepNightKey) ?: return@launch tonight.sleepQuality = quality database.update(tonight) - } // Setting this state variable to true will alert the observer and trigger navigation. _navigateToSleepTracker.value = true diff --git a/TrackMySleepQualityFinal/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt b/TrackMySleepQualityFinal/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt index ea4aad639..0a4de03f6 100755 --- a/TrackMySleepQualityFinal/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt +++ b/TrackMySleepQualityFinal/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.example.android.trackmysleepquality.database.SleepDatabaseDao import com.example.android.trackmysleepquality.database.SleepNight import com.example.android.trackmysleepquality.formatNights @@ -38,25 +39,6 @@ class SleepTrackerViewModel( val database: SleepDatabaseDao, application: Application) : AndroidViewModel(application) { - /** Coroutine variables */ - - /** - * viewModelJob allows us to cancel all coroutines started by this ViewModel. - */ - private var viewModelJob = Job() - - /** - * A [CoroutineScope] keeps track of all coroutines started by this ViewModel. - * - * Because we pass it [viewModelJob], any coroutine started in this uiScope can be cancelled - * by calling `viewModelJob.cancel()` - * - * By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is - * the main thread on Android. This is a sensible default because most coroutines started by - * a [ViewModel] update the UI after performing some processing. - */ - private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) - private var tonight = MutableLiveData() private val nights = database.getAllNights() @@ -141,7 +123,7 @@ class SleepTrackerViewModel( } private fun initializeTonight() { - uiScope.launch { + viewModelScope.launch { tonight.value = getTonightFromDatabase() } } @@ -154,38 +136,30 @@ class SleepTrackerViewModel( * recording. */ private suspend fun getTonightFromDatabase(): SleepNight? { - return withContext(Dispatchers.IO) { var night = database.getTonight() if (night?.endTimeMilli != night?.startTimeMilli) { night = null } - night - } + return night } private suspend fun insert(night: SleepNight) { - withContext(Dispatchers.IO) { database.insert(night) - } } private suspend fun update(night: SleepNight) { - withContext(Dispatchers.IO) { database.update(night) - } } private suspend fun clear() { - withContext(Dispatchers.IO) { database.clear() - } } /** * Executes when the START button is clicked. */ fun onStartTracking() { - uiScope.launch { + viewModelScope.launch { // Create a new night, which captures the current time, // and insert it into the database. val newNight = SleepNight() @@ -200,7 +174,7 @@ class SleepTrackerViewModel( * Executes when the STOP button is clicked. */ fun onStopTracking() { - uiScope.launch { + viewModelScope.launch { // In Kotlin, the return@label syntax is used for specifying which function among // several nested ones this statement returns from. // In this case, we are specifying to return from launch(), @@ -234,13 +208,4 @@ class SleepTrackerViewModel( } /** - * Called when the ViewModel is dismantled. - * At this point, we want to cancel all coroutines; - * otherwise we end up with processes that have nowhere to return to - * using memory and resources. - */ - override fun onCleared() { - super.onCleared() - viewModelJob.cancel() - } } \ No newline at end of file diff --git a/TrackMySleepQualityRoomAndTesting/app/build.gradle b/TrackMySleepQualityRoomAndTesting/app/build.gradle index 618341513..28e7011b6 100755 --- a/TrackMySleepQualityRoomAndTesting/app/build.gradle +++ b/TrackMySleepQualityRoomAndTesting/app/build.gradle @@ -53,6 +53,10 @@ dependencies { implementation "androidx.appcompat:appcompat:1.2.0" implementation "androidx.fragment:fragment:1.2.5" implementation "androidx.constraintlayout:constraintlayout:2.0.0-rc1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" + + // Kotlin Extensions and Coroutines support for Room + implementation "androidx.room:room-ktx:$room_version" // Android KTX implementation 'androidx.core:core-ktx:1.3.1'