Skip to content
This repository was archived by the owner on Jan 11, 2024. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e94638a

Browse files
committedAug 26, 2020
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 moves database calls onto 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.
1 parent 77fff5c commit e94638a

File tree

37 files changed

+161
-494
lines changed

37 files changed

+161
-494
lines changed
 

‎DevBytes-starter/app/build.gradle

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ android {
2828
targetSdkVersion 30
2929
versionCode 1
3030
versionName "1.0"
31-
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
3231
vectorDrawables.useSupportLibrary = true
32+
multiDexEnabled true
3333
}
3434
buildTypes {
3535
release {
@@ -54,7 +54,7 @@ dependencies {
5454
implementation 'com.google.android.material:material:1.0.0'
5555

5656
// Android KTX
57-
implementation 'androidx.core:core-ktx:1.0.2'
57+
implementation 'androidx.core:core-ktx:1.3.1'
5858

5959
// constraint layout
6060
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
@@ -87,6 +87,7 @@ dependencies {
8787
// ViewModel and LiveData
8888
def lifecycle_version = "2.2.0"
8989
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
90+
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
9091

9192
// logging
9293
implementation 'com.jakewharton.timber:timber:4.7.1'

‎DevBytes-starter/app/src/main/java/com/example/android/devbyteviewer/viewmodels/DevByteViewModel.kt

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.lifecycle.LiveData
2222
import androidx.lifecycle.MutableLiveData
2323
import androidx.lifecycle.ViewModel
2424
import androidx.lifecycle.ViewModelProvider
25+
import androidx.lifecycle.viewModelScope
2526
import com.example.android.devbyteviewer.domain.DevByteVideo
2627
import com.example.android.devbyteviewer.network.DevByteNetwork
2728
import com.example.android.devbyteviewer.network.asDomainModel
@@ -40,21 +41,6 @@ import java.io.IOException
4041
*/
4142
class DevByteViewModel(application: Application) : AndroidViewModel(application) {
4243

43-
/**
44-
* This is the job for all coroutines started by this ViewModel.
45-
*
46-
* Cancelling this job will cancel all coroutines started by this ViewModel.
47-
*/
48-
private val viewModelJob = SupervisorJob()
49-
50-
/**
51-
* This is the main scope for all coroutines launched by MainViewModel.
52-
*
53-
* Since we pass viewModelJob, you can cancel all coroutines launched by uiScope by calling
54-
* viewModelJob.cancel()
55-
*/
56-
private val viewModelScope = CoroutineScope(viewModelJob + Dispatchers.Main)
57-
5844
/**
5945
* A playlist of videos that can be shown on the screen. This is private to avoid exposing a
6046
* way to set this value to observers.
@@ -129,15 +115,6 @@ class DevByteViewModel(application: Application) : AndroidViewModel(application)
129115
_isNetworkErrorShown.value = true
130116
}
131117

132-
133-
/**
134-
* Cancel all coroutines when the ViewModel is cleared
135-
*/
136-
override fun onCleared() {
137-
super.onCleared()
138-
viewModelJob.cancel()
139-
}
140-
141118
/**
142119
* Factory for constructing DevByteViewModel with parameter
143120
*/

‎RecyclerViewClickHandler-Starter/app/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ dependencies {
6363
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
6464
kapt "androidx.room:room-compiler:$room_version"
6565
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
66+
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
67+
68+
// Kotlin Extensions and Coroutines support for Room
69+
implementation "androidx.room:room-ktx:$room_version"
6670

6771
// Coroutines
6872
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"

‎RecyclerViewClickHandler-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import androidx.room.Update
3131
interface SleepDatabaseDao {
3232

3333
@Insert
34-
fun insert(night: SleepNight)
34+
suspend fun insert(night: SleepNight)
3535

3636
/**
3737
* When updating a row with a value already set in a column,
@@ -40,23 +40,23 @@ interface SleepDatabaseDao {
4040
* @param night new value to write
4141
*/
4242
@Update
43-
fun update(night: SleepNight)
43+
suspend fun update(night: SleepNight)
4444

4545
/**
4646
* Selects and returns the row that matches the supplied start time, which is our key.
4747
*
4848
* @param key startTimeMilli to match
4949
*/
5050
@Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
51-
fun get(key: Long): SleepNight
51+
suspend fun get(key: Long): SleepNight
5252

5353
/**
5454
* Deletes all values from the table.
5555
*
5656
* This does not delete the table, only its contents.
5757
*/
5858
@Query("DELETE FROM daily_sleep_quality_table")
59-
fun clear()
59+
suspend fun clear()
6060

6161
/**
6262
* Selects and returns all rows in the table,
@@ -70,7 +70,7 @@ interface SleepDatabaseDao {
7070
* Selects and returns the latest night.
7171
*/
7272
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
73-
fun getTonight(): SleepNight?
73+
suspend fun getTonight(): SleepNight?
7474

7575
/**
7676
* Selects and returns the night with given nightId.

‎RecyclerViewClickHandler-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepdetail/SleepDetailFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class SleepDetailFragment : Fragment() {
6767
binding.setLifecycleOwner(this)
6868

6969
// Add an Observer to the state variable for Navigating when a Quality icon is tapped.
70-
sleepDetailViewModel.navigateToSleepTracker.observe(this, Observer {
70+
sleepDetailViewModel.navigateToSleepTracker.observe(viewLifecycleOwner, Observer {
7171
if (it == true) { // Observed state is true.
7272
this.findNavController().navigate(
7373
SleepDetailFragmentDirections.actionSleepDetailFragmentToSleepTrackerFragment())

‎RecyclerViewClickHandler-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepdetail/SleepDetailViewModel.kt

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,6 @@ class SleepDetailViewModel(
4242
*/
4343
val database = dataSource
4444

45-
/** Coroutine setup variables */
46-
47-
/**
48-
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
49-
*/
50-
private val viewModelJob = Job()
51-
5245
private val night: LiveData<SleepNight>
5346

5447
fun getNight() = night
@@ -72,16 +65,6 @@ class SleepDetailViewModel(
7265
val navigateToSleepTracker: LiveData<Boolean?>
7366
get() = _navigateToSleepTracker
7467

75-
/**
76-
* Cancels all coroutines when the ViewModel is cleared, to cleanup any pending work.
77-
*
78-
* onCleared() gets called when the ViewModel is destroyed.
79-
*/
80-
override fun onCleared() {
81-
super.onCleared()
82-
viewModelJob.cancel()
83-
}
84-
8568

8669
/**
8770
* Call this immediately after navigating to [SleepTrackerFragment]

‎RecyclerViewClickHandler-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class SleepQualityFragment : Fragment() {
7070
binding.sleepQualityViewModel = sleepQualityViewModel
7171

7272
// Add an Observer to the state variable for Navigating when a Quality icon is tapped.
73-
sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer {
73+
sleepQualityViewModel.navigateToSleepTracker.observe(viewLifecycleOwner, Observer {
7474
if (it == true) { // Observed state is true.
7575
this.findNavController().navigate(
7676
SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment())

‎RecyclerViewClickHandler-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt

Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,9 @@ package com.example.android.trackmysleepquality.sleepquality
1919
import androidx.lifecycle.LiveData
2020
import androidx.lifecycle.MutableLiveData
2121
import androidx.lifecycle.ViewModel
22+
import androidx.lifecycle.viewModelScope
2223
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
23-
import kotlinx.coroutines.CoroutineScope
24-
import kotlinx.coroutines.Dispatchers
25-
import kotlinx.coroutines.Job
2624
import kotlinx.coroutines.launch
27-
import kotlinx.coroutines.withContext
2825

2926
/**
3027
* ViewModel for SleepQualityFragment.
@@ -40,25 +37,6 @@ class SleepQualityViewModel(
4037
*/
4138
val database = dataSource
4239

43-
/** Coroutine setup variables */
44-
45-
/**
46-
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
47-
*/
48-
private val viewModelJob = Job()
49-
50-
/**
51-
* A [CoroutineScope] keeps track of all coroutines started by this ViewModel.
52-
*
53-
* Because we pass it [viewModelJob], any coroutine started in this scope can be cancelled
54-
* by calling `viewModelJob.cancel()`
55-
*
56-
* By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is
57-
* the main thread on Android. This is a sensible default because most coroutines started by
58-
* a [ViewModel] update the UI after performing some processing.
59-
*/
60-
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
61-
6240
/**
6341
* Variable that tells the fragment whether it should navigate to [SleepTrackerFragment].
6442
*
@@ -73,17 +51,6 @@ class SleepQualityViewModel(
7351
val navigateToSleepTracker: LiveData<Boolean?>
7452
get() = _navigateToSleepTracker
7553

76-
/**
77-
* Cancels all coroutines when the ViewModel is cleared, to cleanup any pending work.
78-
*
79-
* onCleared() gets called when the ViewModel is destroyed.
80-
*/
81-
override fun onCleared() {
82-
super.onCleared()
83-
viewModelJob.cancel()
84-
}
85-
86-
8754
/**
8855
* Call this immediately after navigating to [SleepTrackerFragment]
8956
*/
@@ -97,14 +64,10 @@ class SleepQualityViewModel(
9764
* Then navigates back to the SleepTrackerFragment.
9865
*/
9966
fun onSetSleepQuality(quality: Int) {
100-
uiScope.launch {
101-
// IO is a thread pool for running operations that access the disk, such as
102-
// our Room database.
103-
withContext(Dispatchers.IO) {
104-
val tonight = database.get(sleepNightKey)
105-
tonight.sleepQuality = quality
106-
database.update(tonight)
107-
}
67+
viewModelScope.launch {
68+
val tonight = database.get(sleepNightKey)
69+
tonight.sleepQuality = quality
70+
database.update(tonight)
10871

10972
// Setting this state variable to true will alert the observer and trigger navigation.
11073
_navigateToSleepTracker.value = true

‎RecyclerViewClickHandler-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ class SleepTrackerFragment : Fragment() {
8585

8686
// Add an Observer on the state variable for showing a Snackbar message
8787
// when the CLEAR button is pressed.
88-
sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer {
88+
sleepTrackerViewModel.showSnackBarEvent.observe(viewLifecycleOwner, Observer {
8989
if (it == true) { // Observed state is true.
9090
Snackbar.make(
91-
activity!!.findViewById(android.R.id.content),
91+
requireActivity().findViewById(android.R.id.content),
9292
getString(R.string.cleared_message),
9393
Snackbar.LENGTH_SHORT // How long to display the message.
9494
).show()
@@ -99,7 +99,7 @@ class SleepTrackerFragment : Fragment() {
9999
})
100100

101101
// Add an Observer on the state variable for Navigating when STOP button is pressed.
102-
sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer { night ->
102+
sleepTrackerViewModel.navigateToSleepQuality.observe(viewLifecycleOwner, Observer { night ->
103103
night?.let {
104104
// We need to get the navController from this, because button is not ready, and it
105105
// just has to be a view. For some reason, this only matters if we hit stop again

‎RecyclerViewClickHandler-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt

Lines changed: 12 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,11 @@ import androidx.lifecycle.LiveData
2121
import androidx.lifecycle.MutableLiveData
2222
import androidx.lifecycle.Transformations
2323
import androidx.lifecycle.ViewModel
24+
import androidx.lifecycle.viewModelScope
2425
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
2526
import com.example.android.trackmysleepquality.database.SleepNight
2627
import com.example.android.trackmysleepquality.formatNights
27-
import kotlinx.coroutines.CoroutineScope
28-
import kotlinx.coroutines.Dispatchers
29-
import kotlinx.coroutines.Job
3028
import kotlinx.coroutines.launch
31-
import kotlinx.coroutines.withContext
3229

3330
/**
3431
* ViewModel for SleepTrackerFragment.
@@ -42,25 +39,6 @@ class SleepTrackerViewModel(
4239
*/
4340
val database = dataSource
4441

45-
/** Coroutine variables */
46-
47-
/**
48-
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
49-
*/
50-
private var viewModelJob = Job()
51-
52-
/**
53-
* A [CoroutineScope] keeps track of all coroutines started by this ViewModel.
54-
*
55-
* Because we pass it [viewModelJob], any coroutine started in this uiScope can be cancelled
56-
* by calling `viewModelJob.cancel()`
57-
*
58-
* By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is
59-
* the main thread on Android. This is a sensible default because most coroutines started by
60-
* a [ViewModel] update the UI after performing some processing.
61-
*/
62-
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
63-
6442
private var tonight = MutableLiveData<SleepNight?>()
6543

6644
val nights = database.getAllNights()
@@ -93,7 +71,6 @@ class SleepTrackerViewModel(
9371
it?.isNotEmpty()
9472
}
9573

96-
9774
/**
9875
* Request a toast by setting this value to true.
9976
*
@@ -145,7 +122,7 @@ class SleepTrackerViewModel(
145122
}
146123

147124
private fun initializeTonight() {
148-
uiScope.launch {
125+
viewModelScope.launch {
149126
tonight.value = getTonightFromDatabase()
150127
}
151128
}
@@ -158,38 +135,30 @@ class SleepTrackerViewModel(
158135
* recording.
159136
*/
160137
private suspend fun getTonightFromDatabase(): SleepNight? {
161-
return withContext(Dispatchers.IO) {
162-
var night = database.getTonight()
163-
if (night?.endTimeMilli != night?.startTimeMilli) {
164-
night = null
165-
}
166-
night
138+
var night = database.getTonight()
139+
if (night?.endTimeMilli != night?.startTimeMilli) {
140+
night = null
167141
}
142+
return night
168143
}
169144

170145
private suspend fun insert(night: SleepNight) {
171-
withContext(Dispatchers.IO) {
172-
database.insert(night)
173-
}
146+
database.insert(night)
174147
}
175148

176149
private suspend fun update(night: SleepNight) {
177-
withContext(Dispatchers.IO) {
178-
database.update(night)
179-
}
150+
database.update(night)
180151
}
181152

182153
private suspend fun clear() {
183-
withContext(Dispatchers.IO) {
184-
database.clear()
185-
}
154+
database.clear()
186155
}
187156

188157
/**
189158
* Executes when the START button is clicked.
190159
*/
191160
fun onStart() {
192-
uiScope.launch {
161+
viewModelScope.launch {
193162
// Create a new night, which captures the current time,
194163
// and insert it into the database.
195164
val newNight = SleepNight()
@@ -204,7 +173,7 @@ class SleepTrackerViewModel(
204173
* Executes when the STOP button is clicked.
205174
*/
206175
fun onStop() {
207-
uiScope.launch {
176+
viewModelScope.launch {
208177
// In Kotlin, the return@label syntax is used for specifying which function among
209178
// several nested ones this statement returns from.
210179
// In this case, we are specifying to return from launch().
@@ -224,7 +193,7 @@ class SleepTrackerViewModel(
224193
* Executes when the CLEAR button is clicked.
225194
*/
226195
fun onClear() {
227-
uiScope.launch {
196+
viewModelScope.launch {
228197
// Clear the database table.
229198
clear()
230199

@@ -235,15 +204,4 @@ class SleepTrackerViewModel(
235204
_showSnackbarEvent.value = true
236205
}
237206
}
238-
239-
/**
240-
* Called when the ViewModel is dismantled.
241-
* At this point, we want to cancel all coroutines;
242-
* otherwise we end up with processes that have nowhere to return to
243-
* using memory and resources.
244-
*/
245-
override fun onCleared() {
246-
super.onCleared()
247-
viewModelJob.cancel()
248-
}
249207
}

‎RecyclerViewClickHandler-Starter/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ buildscript {
2121
ext {
2222
kotlin_version = '1.3.72'
2323
archLifecycleVersion = '1.1.1'
24-
room_version = '2.2.0'
24+
room_version = '2.2.5'
2525
coroutine_version = '1.3.7'
2626
gradleVersion = '4.0.1'
2727
navigationVersion = '1.0.0-alpha07'

‎RecyclerViewDiffUtilDataBinding-Starter/app/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ dependencies {
6363
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
6464
kapt "androidx.room:room-compiler:$room_version"
6565
implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
66+
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
67+
68+
// Kotlin Extensions and Coroutines support for Room
69+
implementation "androidx.room:room-ktx:$room_version"
6670

6771
// Coroutines
6872
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"

‎RecyclerViewDiffUtilDataBinding-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import androidx.room.Update
3131
interface SleepDatabaseDao {
3232

3333
@Insert
34-
fun insert(night: SleepNight)
34+
suspend fun insert(night: SleepNight)
3535

3636
/**
3737
* When updating a row with a value already set in a column,
@@ -40,23 +40,23 @@ interface SleepDatabaseDao {
4040
* @param night new value to write
4141
*/
4242
@Update
43-
fun update(night: SleepNight)
43+
suspend fun update(night: SleepNight)
4444

4545
/**
4646
* Selects and returns the row that matches the supplied start time, which is our key.
4747
*
4848
* @param key startTimeMilli to match
4949
*/
5050
@Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
51-
fun get(key: Long): SleepNight
51+
suspend fun get(key: Long): SleepNight
5252

5353
/**
5454
* Deletes all values from the table.
5555
*
5656
* This does not delete the table, only its contents.
5757
*/
5858
@Query("DELETE FROM daily_sleep_quality_table")
59-
fun clear()
59+
suspend fun clear()
6060

6161
/**
6262
* Selects and returns all rows in the table,
@@ -70,5 +70,5 @@ interface SleepDatabaseDao {
7070
* Selects and returns the latest night.
7171
*/
7272
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
73-
fun getTonight(): SleepNight?
73+
suspend fun getTonight(): SleepNight?
7474
}

‎RecyclerViewDiffUtilDataBinding-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class SleepQualityFragment : Fragment() {
7070
binding.sleepQualityViewModel = sleepQualityViewModel
7171

7272
// Add an Observer to the state variable for Navigating when a Quality icon is tapped.
73-
sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer {
73+
sleepQualityViewModel.navigateToSleepTracker.observe(viewLifecycleOwner, Observer {
7474
if (it == true) { // Observed state is true.
7575
this.findNavController().navigate(
7676
SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment())

‎RecyclerViewDiffUtilDataBinding-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt

Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,9 @@ package com.example.android.trackmysleepquality.sleepquality
1919
import androidx.lifecycle.LiveData
2020
import androidx.lifecycle.MutableLiveData
2121
import androidx.lifecycle.ViewModel
22+
import androidx.lifecycle.viewModelScope
2223
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
23-
import kotlinx.coroutines.CoroutineScope
24-
import kotlinx.coroutines.Dispatchers
25-
import kotlinx.coroutines.Job
2624
import kotlinx.coroutines.launch
27-
import kotlinx.coroutines.withContext
2825

2926
/**
3027
* ViewModel for SleepQualityFragment.
@@ -40,25 +37,6 @@ class SleepQualityViewModel(
4037
*/
4138
val database = dataSource
4239

43-
/** Coroutine setup variables */
44-
45-
/**
46-
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
47-
*/
48-
private val viewModelJob = Job()
49-
50-
/**
51-
* A [CoroutineScope] keeps track of all coroutines started by this ViewModel.
52-
*
53-
* Because we pass it [viewModelJob], any coroutine started in this scope can be cancelled
54-
* by calling `viewModelJob.cancel()`
55-
*
56-
* By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is
57-
* the main thread on Android. This is a sensible default because most coroutines started by
58-
* a [ViewModel] update the UI after performing some processing.
59-
*/
60-
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
61-
6240
/**
6341
* Variable that tells the fragment whether it should navigate to [SleepTrackerFragment].
6442
*
@@ -73,17 +51,6 @@ class SleepQualityViewModel(
7351
val navigateToSleepTracker: LiveData<Boolean?>
7452
get() = _navigateToSleepTracker
7553

76-
/**
77-
* Cancels all coroutines when the ViewModel is cleared, to cleanup any pending work.
78-
*
79-
* onCleared() gets called when the ViewModel is destroyed.
80-
*/
81-
override fun onCleared() {
82-
super.onCleared()
83-
viewModelJob.cancel()
84-
}
85-
86-
8754
/**
8855
* Call this immediately after navigating to [SleepTrackerFragment]
8956
*/
@@ -97,14 +64,10 @@ class SleepQualityViewModel(
9764
* Then navigates back to the SleepTrackerFragment.
9865
*/
9966
fun onSetSleepQuality(quality: Int) {
100-
uiScope.launch {
101-
// IO is a thread pool for running operations that access the disk, such as
102-
// our Room database.
103-
withContext(Dispatchers.IO) {
104-
val tonight = database.get(sleepNightKey)
105-
tonight.sleepQuality = quality
106-
database.update(tonight)
107-
}
67+
viewModelScope.launch {
68+
val tonight = database.get(sleepNightKey)
69+
tonight.sleepQuality = quality
70+
database.update(tonight)
10871

10972
// Setting this state variable to true will alert the observer and trigger navigation.
11073
_navigateToSleepTracker.value = true

‎RecyclerViewDiffUtilDataBinding-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ class SleepTrackerFragment : Fragment() {
8484

8585
// Add an Observer on the state variable for showing a Snackbar message
8686
// when the CLEAR button is pressed.
87-
sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer {
87+
sleepTrackerViewModel.showSnackBarEvent.observe(viewLifecycleOwner, Observer {
8888
if (it == true) { // Observed state is true.
8989
Snackbar.make(
90-
activity!!.findViewById(android.R.id.content),
90+
requireActivity().findViewById(android.R.id.content),
9191
getString(R.string.cleared_message),
9292
Snackbar.LENGTH_SHORT // How long to display the message.
9393
).show()
@@ -98,7 +98,7 @@ class SleepTrackerFragment : Fragment() {
9898
})
9999

100100
// Add an Observer on the state variable for Navigating when STOP button is pressed.
101-
sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer { night ->
101+
sleepTrackerViewModel.navigateToSleepQuality.observe(viewLifecycleOwner, Observer { night ->
102102
night?.let {
103103
// We need to get the navController from this, because button is not ready, and it
104104
// just has to be a view. For some reason, this only matters if we hit stop again

‎RecyclerViewDiffUtilDataBinding-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt

Lines changed: 12 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,11 @@ import androidx.lifecycle.LiveData
2121
import androidx.lifecycle.MutableLiveData
2222
import androidx.lifecycle.Transformations
2323
import androidx.lifecycle.ViewModel
24+
import androidx.lifecycle.viewModelScope
2425
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
2526
import com.example.android.trackmysleepquality.database.SleepNight
2627
import com.example.android.trackmysleepquality.formatNights
27-
import kotlinx.coroutines.CoroutineScope
28-
import kotlinx.coroutines.Dispatchers
29-
import kotlinx.coroutines.Job
3028
import kotlinx.coroutines.launch
31-
import kotlinx.coroutines.withContext
3229

3330
/**
3431
* ViewModel for SleepTrackerFragment.
@@ -42,25 +39,6 @@ class SleepTrackerViewModel(
4239
*/
4340
val database = dataSource
4441

45-
/** Coroutine variables */
46-
47-
/**
48-
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
49-
*/
50-
private var viewModelJob = Job()
51-
52-
/**
53-
* A [CoroutineScope] keeps track of all coroutines started by this ViewModel.
54-
*
55-
* Because we pass it [viewModelJob], any coroutine started in this uiScope can be cancelled
56-
* by calling `viewModelJob.cancel()`
57-
*
58-
* By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is
59-
* the main thread on Android. This is a sensible default because most coroutines started by
60-
* a [ViewModel] update the UI after performing some processing.
61-
*/
62-
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
63-
6442
private var tonight = MutableLiveData<SleepNight?>()
6543

6644
val nights = database.getAllNights()
@@ -93,7 +71,6 @@ class SleepTrackerViewModel(
9371
it?.isNotEmpty()
9472
}
9573

96-
9774
/**
9875
* Request a toast by setting this value to true.
9976
*
@@ -145,7 +122,7 @@ class SleepTrackerViewModel(
145122
}
146123

147124
private fun initializeTonight() {
148-
uiScope.launch {
125+
viewModelScope.launch {
149126
tonight.value = getTonightFromDatabase()
150127
}
151128
}
@@ -158,38 +135,30 @@ class SleepTrackerViewModel(
158135
* recording.
159136
*/
160137
private suspend fun getTonightFromDatabase(): SleepNight? {
161-
return withContext(Dispatchers.IO) {
162-
var night = database.getTonight()
163-
if (night?.endTimeMilli != night?.startTimeMilli) {
164-
night = null
165-
}
166-
night
138+
var night = database.getTonight()
139+
if (night?.endTimeMilli != night?.startTimeMilli) {
140+
night = null
167141
}
142+
return night
168143
}
169144

170145
private suspend fun insert(night: SleepNight) {
171-
withContext(Dispatchers.IO) {
172-
database.insert(night)
173-
}
146+
database.insert(night)
174147
}
175148

176149
private suspend fun update(night: SleepNight) {
177-
withContext(Dispatchers.IO) {
178-
database.update(night)
179-
}
150+
database.update(night)
180151
}
181152

182153
private suspend fun clear() {
183-
withContext(Dispatchers.IO) {
184-
database.clear()
185-
}
154+
database.clear()
186155
}
187156

188157
/**
189158
* Executes when the START button is clicked.
190159
*/
191160
fun onStart() {
192-
uiScope.launch {
161+
viewModelScope.launch {
193162
// Create a new night, which captures the current time,
194163
// and insert it into the database.
195164
val newNight = SleepNight()
@@ -204,7 +173,7 @@ class SleepTrackerViewModel(
204173
* Executes when the STOP button is clicked.
205174
*/
206175
fun onStop() {
207-
uiScope.launch {
176+
viewModelScope.launch {
208177
// In Kotlin, the return@label syntax is used for specifying which function among
209178
// several nested ones this statement returns from.
210179
// In this case, we are specifying to return from launch().
@@ -224,7 +193,7 @@ class SleepTrackerViewModel(
224193
* Executes when the CLEAR button is clicked.
225194
*/
226195
fun onClear() {
227-
uiScope.launch {
196+
viewModelScope.launch {
228197
// Clear the database table.
229198
clear()
230199

@@ -235,15 +204,4 @@ class SleepTrackerViewModel(
235204
_showSnackbarEvent.value = true
236205
}
237206
}
238-
239-
/**
240-
* Called when the ViewModel is dismantled.
241-
* At this point, we want to cancel all coroutines;
242-
* otherwise we end up with processes that have nowhere to return to
243-
* using memory and resources.
244-
*/
245-
override fun onCleared() {
246-
super.onCleared()
247-
viewModelJob.cancel()
248-
}
249207
}

‎RecyclerViewDiffUtilDataBinding-Starter/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ buildscript {
2121
ext {
2222
kotlin_version = '1.3.72'
2323
archLifecycleVersion = '1.1.1'
24-
room_version = '2.2.0'
24+
room_version = '2.2.5'
2525
coroutine_version = '1.3.7'
2626
gradleVersion = '4.0.1'
2727
navigationVersion = '1.0.0-alpha07'

‎RecyclerViewFundamentals-Starter/app/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ dependencies {
6363
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
6464
kapt "androidx.room:room-compiler:$room_version"
6565
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
66+
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
67+
68+
// Kotlin Extensions and Coroutines support for Room
69+
implementation "androidx.room:room-ktx:$room_version"
6670

6771
// Coroutines
6872
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"

‎RecyclerViewFundamentals-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import androidx.room.Update
3131
interface SleepDatabaseDao {
3232

3333
@Insert
34-
fun insert(night: SleepNight)
34+
suspend fun insert(night: SleepNight)
3535

3636
/**
3737
* When updating a row with a value already set in a column,
@@ -40,23 +40,23 @@ interface SleepDatabaseDao {
4040
* @param night new value to write
4141
*/
4242
@Update
43-
fun update(night: SleepNight)
43+
suspend fun update(night: SleepNight)
4444

4545
/**
4646
* Selects and returns the row that matches the supplied start time, which is our key.
4747
*
4848
* @param key startTimeMilli to match
4949
*/
5050
@Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
51-
fun get(key: Long): SleepNight
51+
suspend fun get(key: Long): SleepNight
5252

5353
/**
5454
* Deletes all values from the table.
5555
*
5656
* This does not delete the table, only its contents.
5757
*/
5858
@Query("DELETE FROM daily_sleep_quality_table")
59-
fun clear()
59+
suspend fun clear()
6060

6161
/**
6262
* Selects and returns all rows in the table,
@@ -70,5 +70,5 @@ interface SleepDatabaseDao {
7070
* Selects and returns the latest night.
7171
*/
7272
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
73-
fun getTonight(): SleepNight?
73+
suspend fun getTonight(): SleepNight?
7474
}

‎RecyclerViewFundamentals-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class SleepQualityFragment : Fragment() {
7070
binding.sleepQualityViewModel = sleepQualityViewModel
7171

7272
// Add an Observer to the state variable for Navigating when a Quality icon is tapped.
73-
sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer {
73+
sleepQualityViewModel.navigateToSleepTracker.observe(viewLifecycleOwner, Observer {
7474
if (it == true) { // Observed state is true.
7575
this.findNavController().navigate(
7676
SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment())

‎RecyclerViewFundamentals-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,9 @@ package com.example.android.trackmysleepquality.sleepquality
1919
import androidx.lifecycle.LiveData
2020
import androidx.lifecycle.MutableLiveData
2121
import androidx.lifecycle.ViewModel
22+
import androidx.lifecycle.viewModelScope
2223
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
23-
import kotlinx.coroutines.CoroutineScope
24-
import kotlinx.coroutines.Dispatchers
25-
import kotlinx.coroutines.Job
2624
import kotlinx.coroutines.launch
27-
import kotlinx.coroutines.withContext
2825

2926
/**
3027
* ViewModel for SleepQualityFragment.
@@ -40,25 +37,6 @@ class SleepQualityViewModel(
4037
*/
4138
val database = dataSource
4239

43-
/** Coroutine setup variables */
44-
45-
/**
46-
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
47-
*/
48-
private val viewModelJob = Job()
49-
50-
/**
51-
* A [CoroutineScope] keeps track of all coroutines started by this ViewModel.
52-
*
53-
* Because we pass it [viewModelJob], any coroutine started in this scope can be cancelled
54-
* by calling `viewModelJob.cancel()`
55-
*
56-
* By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is
57-
* the main thread on Android. This is a sensible default because most coroutines started by
58-
* a [ViewModel] update the UI after performing some processing.
59-
*/
60-
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
61-
6240
/**
6341
* Variable that tells the fragment whether it should navigate to [SleepTrackerFragment].
6442
*
@@ -73,16 +51,6 @@ class SleepQualityViewModel(
7351
val navigateToSleepTracker: LiveData<Boolean?>
7452
get() = _navigateToSleepTracker
7553

76-
/**
77-
* Cancels all coroutines when the ViewModel is cleared, to cleanup any pending work.
78-
*
79-
* onCleared() gets called when the ViewModel is destroyed.
80-
*/
81-
override fun onCleared() {
82-
super.onCleared()
83-
viewModelJob.cancel()
84-
}
85-
8654

8755
/**
8856
* Call this immediately after navigating to [SleepTrackerFragment]
@@ -97,14 +65,10 @@ class SleepQualityViewModel(
9765
* Then navigates back to the SleepTrackerFragment.
9866
*/
9967
fun onSetSleepQuality(quality: Int) {
100-
uiScope.launch {
101-
// IO is a thread pool for running operations that access the disk, such as
102-
// our Room database.
103-
withContext(Dispatchers.IO) {
68+
viewModelScope.launch {
10469
val tonight = database.get(sleepNightKey)
10570
tonight.sleepQuality = quality
10671
database.update(tonight)
107-
}
10872

10973
// Setting this state variable to true will alert the observer and trigger navigation.
11074
_navigateToSleepTracker.value = true

‎RecyclerViewFundamentals-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@ class SleepTrackerFragment : Fragment() {
7575

7676
// Add an Observer on the state variable for showing a Snackbar message
7777
// when the CLEAR button is pressed.
78-
sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer {
78+
sleepTrackerViewModel.showSnackBarEvent.observe(viewLifecycleOwner, Observer {
7979
if (it == true) { // Observed state is true.
8080
Snackbar.make(
81-
activity!!.findViewById(android.R.id.content),
81+
requireActivity().findViewById(android.R.id.content),
8282
getString(R.string.cleared_message),
8383
Snackbar.LENGTH_SHORT // How long to display the message.
8484
).show()
@@ -89,7 +89,7 @@ class SleepTrackerFragment : Fragment() {
8989
})
9090

9191
// Add an Observer on the state variable for Navigating when STOP button is pressed.
92-
sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer { night ->
92+
sleepTrackerViewModel.navigateToSleepQuality.observe(viewLifecycleOwner, Observer { night ->
9393
night?.let {
9494
// We need to get the navController from this, because button is not ready, and it
9595
// just has to be a view. For some reason, this only matters if we hit stop again

‎RecyclerViewFundamentals-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt

Lines changed: 6 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,11 @@ import androidx.lifecycle.LiveData
2121
import androidx.lifecycle.MutableLiveData
2222
import androidx.lifecycle.Transformations
2323
import androidx.lifecycle.ViewModel
24+
import androidx.lifecycle.viewModelScope
2425
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
2526
import com.example.android.trackmysleepquality.database.SleepNight
2627
import com.example.android.trackmysleepquality.formatNights
27-
import kotlinx.coroutines.CoroutineScope
28-
import kotlinx.coroutines.Dispatchers
29-
import kotlinx.coroutines.Job
3028
import kotlinx.coroutines.launch
31-
import kotlinx.coroutines.withContext
3229

3330
/**
3431
* ViewModel for SleepTrackerFragment.
@@ -42,25 +39,6 @@ class SleepTrackerViewModel(
4239
*/
4340
val database = dataSource
4441

45-
/** Coroutine variables */
46-
47-
/**
48-
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
49-
*/
50-
private var viewModelJob = Job()
51-
52-
/**
53-
* A [CoroutineScope] keeps track of all coroutines started by this ViewModel.
54-
*
55-
* Because we pass it [viewModelJob], any coroutine started in this uiScope can be cancelled
56-
* by calling `viewModelJob.cancel()`
57-
*
58-
* By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is
59-
* the main thread on Android. This is a sensible default because most coroutines started by
60-
* a [ViewModel] update the UI after performing some processing.
61-
*/
62-
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
63-
6442
private var tonight = MutableLiveData<SleepNight?>()
6543

6644
private val nights = database.getAllNights()
@@ -93,7 +71,6 @@ class SleepTrackerViewModel(
9371
it?.isNotEmpty()
9472
}
9573

96-
9774
/**
9875
* Request a toast by setting this value to true.
9976
*
@@ -145,7 +122,7 @@ class SleepTrackerViewModel(
145122
}
146123

147124
private fun initializeTonight() {
148-
uiScope.launch {
125+
viewModelScope.launch {
149126
tonight.value = getTonightFromDatabase()
150127
}
151128
}
@@ -158,38 +135,30 @@ class SleepTrackerViewModel(
158135
* recording.
159136
*/
160137
private suspend fun getTonightFromDatabase(): SleepNight? {
161-
return withContext(Dispatchers.IO) {
162138
var night = database.getTonight()
163139
if (night?.endTimeMilli != night?.startTimeMilli) {
164140
night = null
165141
}
166-
night
167-
}
142+
return night
168143
}
169144

170145
private suspend fun insert(night: SleepNight) {
171-
withContext(Dispatchers.IO) {
172146
database.insert(night)
173-
}
174147
}
175148

176149
private suspend fun update(night: SleepNight) {
177-
withContext(Dispatchers.IO) {
178150
database.update(night)
179-
}
180151
}
181152

182153
private suspend fun clear() {
183-
withContext(Dispatchers.IO) {
184154
database.clear()
185-
}
186155
}
187156

188157
/**
189158
* Executes when the START button is clicked.
190159
*/
191160
fun onStart() {
192-
uiScope.launch {
161+
viewModelScope.launch {
193162
// Create a new night, which captures the current time,
194163
// and insert it into the database.
195164
val newNight = SleepNight()
@@ -204,7 +173,7 @@ class SleepTrackerViewModel(
204173
* Executes when the STOP button is clicked.
205174
*/
206175
fun onStop() {
207-
uiScope.launch {
176+
viewModelScope.launch {
208177
// In Kotlin, the return@label syntax is used for specifying which function among
209178
// several nested ones this statement returns from.
210179
// In this case, we are specifying to return from launch().
@@ -224,7 +193,7 @@ class SleepTrackerViewModel(
224193
* Executes when the CLEAR button is clicked.
225194
*/
226195
fun onClear() {
227-
uiScope.launch {
196+
viewModelScope.launch {
228197
// Clear the database table.
229198
clear()
230199

@@ -235,15 +204,4 @@ class SleepTrackerViewModel(
235204
_showSnackbarEvent.value = true
236205
}
237206
}
238-
239-
/**
240-
* Called when the ViewModel is dismantled.
241-
* At this point, we want to cancel all coroutines;
242-
* otherwise we end up with processes that have nowhere to return to
243-
* using memory and resources.
244-
*/
245-
override fun onCleared() {
246-
super.onCleared()
247-
viewModelJob.cancel()
248-
}
249207
}

‎RecyclerViewFundamentals-Starter/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ buildscript {
2121
ext {
2222
kotlin_version = '1.3.72'
2323
archLifecycleVersion = '1.1.1'
24-
room_version = '2.2.0'
24+
room_version = '2.2.5'
2525
coroutine_version = '1.3.7'
2626
gradleVersion = '4.0.1'
2727
navigationVersion = '1.0.0-alpha07'

‎RecyclerViewGridLayout-Starter/app/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ dependencies {
6363
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
6464
kapt "androidx.room:room-compiler:$room_version"
6565
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
66+
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
67+
68+
// Kotlin Extensions and Coroutines support for Room
69+
implementation "androidx.room:room-ktx:$room_version"
6670

6771
// Coroutines
6872
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"

‎RecyclerViewGridLayout-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import androidx.room.Update
3131
interface SleepDatabaseDao {
3232

3333
@Insert
34-
fun insert(night: SleepNight)
34+
suspend fun insert(night: SleepNight)
3535

3636
/**
3737
* When updating a row with a value already set in a column,
@@ -40,23 +40,23 @@ interface SleepDatabaseDao {
4040
* @param night new value to write
4141
*/
4242
@Update
43-
fun update(night: SleepNight)
43+
suspend fun update(night: SleepNight)
4444

4545
/**
4646
* Selects and returns the row that matches the supplied start time, which is our key.
4747
*
4848
* @param key startTimeMilli to match
4949
*/
5050
@Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
51-
fun get(key: Long): SleepNight
51+
suspend fun get(key: Long): SleepNight
5252

5353
/**
5454
* Deletes all values from the table.
5555
*
5656
* This does not delete the table, only its contents.
5757
*/
5858
@Query("DELETE FROM daily_sleep_quality_table")
59-
fun clear()
59+
suspend fun clear()
6060

6161
/**
6262
* Selects and returns all rows in the table,
@@ -70,5 +70,5 @@ interface SleepDatabaseDao {
7070
* Selects and returns the latest night.
7171
*/
7272
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
73-
fun getTonight(): SleepNight?
73+
suspend fun getTonight(): SleepNight?
7474
}

‎RecyclerViewGridLayout-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class SleepQualityFragment : Fragment() {
7070
binding.sleepQualityViewModel = sleepQualityViewModel
7171

7272
// Add an Observer to the state variable for Navigating when a Quality icon is tapped.
73-
sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer {
73+
sleepQualityViewModel.navigateToSleepTracker.observe(viewLifecycleOwner, Observer {
7474
if (it == true) { // Observed state is true.
7575
this.findNavController().navigate(
7676
SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment())

‎RecyclerViewGridLayout-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt

Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,9 @@ package com.example.android.trackmysleepquality.sleepquality
1919
import androidx.lifecycle.LiveData
2020
import androidx.lifecycle.MutableLiveData
2121
import androidx.lifecycle.ViewModel
22+
import androidx.lifecycle.viewModelScope
2223
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
23-
import kotlinx.coroutines.CoroutineScope
24-
import kotlinx.coroutines.Dispatchers
25-
import kotlinx.coroutines.Job
2624
import kotlinx.coroutines.launch
27-
import kotlinx.coroutines.withContext
2825

2926
/**
3027
* ViewModel for SleepQualityFragment.
@@ -40,25 +37,6 @@ class SleepQualityViewModel(
4037
*/
4138
val database = dataSource
4239

43-
/** Coroutine setup variables */
44-
45-
/**
46-
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
47-
*/
48-
private val viewModelJob = Job()
49-
50-
/**
51-
* A [CoroutineScope] keeps track of all coroutines started by this ViewModel.
52-
*
53-
* Because we pass it [viewModelJob], any coroutine started in this scope can be cancelled
54-
* by calling `viewModelJob.cancel()`
55-
*
56-
* By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is
57-
* the main thread on Android. This is a sensible default because most coroutines started by
58-
* a [ViewModel] update the UI after performing some processing.
59-
*/
60-
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
61-
6240
/**
6341
* Variable that tells the fragment whether it should navigate to [SleepTrackerFragment].
6442
*
@@ -73,17 +51,6 @@ class SleepQualityViewModel(
7351
val navigateToSleepTracker: LiveData<Boolean?>
7452
get() = _navigateToSleepTracker
7553

76-
/**
77-
* Cancels all coroutines when the ViewModel is cleared, to cleanup any pending work.
78-
*
79-
* onCleared() gets called when the ViewModel is destroyed.
80-
*/
81-
override fun onCleared() {
82-
super.onCleared()
83-
viewModelJob.cancel()
84-
}
85-
86-
8754
/**
8855
* Call this immediately after navigating to [SleepTrackerFragment]
8956
*/
@@ -97,14 +64,10 @@ class SleepQualityViewModel(
9764
* Then navigates back to the SleepTrackerFragment.
9865
*/
9966
fun onSetSleepQuality(quality: Int) {
100-
uiScope.launch {
101-
// IO is a thread pool for running operations that access the disk, such as
102-
// our Room database.
103-
withContext(Dispatchers.IO) {
104-
val tonight = database.get(sleepNightKey)
105-
tonight.sleepQuality = quality
106-
database.update(tonight)
107-
}
67+
viewModelScope.launch {
68+
val tonight = database.get(sleepNightKey)
69+
tonight.sleepQuality = quality
70+
database.update(tonight)
10871

10972
// Setting this state variable to true will alert the observer and trigger navigation.
11073
_navigateToSleepTracker.value = true

‎RecyclerViewGridLayout-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ class SleepTrackerFragment : Fragment() {
8484

8585
// Add an Observer on the state variable for showing a Snackbar message
8686
// when the CLEAR button is pressed.
87-
sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer {
87+
sleepTrackerViewModel.showSnackBarEvent.observe(viewLifecycleOwner, Observer {
8888
if (it == true) { // Observed state is true.
8989
Snackbar.make(
90-
activity!!.findViewById(android.R.id.content),
90+
requireActivity().findViewById(android.R.id.content),
9191
getString(R.string.cleared_message),
9292
Snackbar.LENGTH_SHORT // How long to display the message.
9393
).show()
@@ -98,7 +98,7 @@ class SleepTrackerFragment : Fragment() {
9898
})
9999

100100
// Add an Observer on the state variable for Navigating when STOP button is pressed.
101-
sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer { night ->
101+
sleepTrackerViewModel.navigateToSleepQuality.observe(viewLifecycleOwner, Observer { night ->
102102
night?.let {
103103
// We need to get the navController from this, because button is not ready, and it
104104
// just has to be a view. For some reason, this only matters if we hit stop again

‎RecyclerViewGridLayout-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt

Lines changed: 12 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,11 @@ import androidx.lifecycle.LiveData
2121
import androidx.lifecycle.MutableLiveData
2222
import androidx.lifecycle.Transformations
2323
import androidx.lifecycle.ViewModel
24+
import androidx.lifecycle.viewModelScope
2425
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
2526
import com.example.android.trackmysleepquality.database.SleepNight
2627
import com.example.android.trackmysleepquality.formatNights
27-
import kotlinx.coroutines.CoroutineScope
28-
import kotlinx.coroutines.Dispatchers
29-
import kotlinx.coroutines.Job
3028
import kotlinx.coroutines.launch
31-
import kotlinx.coroutines.withContext
3229

3330
/**
3431
* ViewModel for SleepTrackerFragment.
@@ -42,25 +39,6 @@ class SleepTrackerViewModel(
4239
*/
4340
val database = dataSource
4441

45-
/** Coroutine variables */
46-
47-
/**
48-
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
49-
*/
50-
private var viewModelJob = Job()
51-
52-
/**
53-
* A [CoroutineScope] keeps track of all coroutines started by this ViewModel.
54-
*
55-
* Because we pass it [viewModelJob], any coroutine started in this uiScope can be cancelled
56-
* by calling `viewModelJob.cancel()`
57-
*
58-
* By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is
59-
* the main thread on Android. This is a sensible default because most coroutines started by
60-
* a [ViewModel] update the UI after performing some processing.
61-
*/
62-
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
63-
6442
private var tonight = MutableLiveData<SleepNight?>()
6543

6644
val nights = database.getAllNights()
@@ -93,7 +71,6 @@ class SleepTrackerViewModel(
9371
it?.isNotEmpty()
9472
}
9573

96-
9774
/**
9875
* Request a toast by setting this value to true.
9976
*
@@ -145,7 +122,7 @@ class SleepTrackerViewModel(
145122
}
146123

147124
private fun initializeTonight() {
148-
uiScope.launch {
125+
viewModelScope.launch {
149126
tonight.value = getTonightFromDatabase()
150127
}
151128
}
@@ -158,38 +135,30 @@ class SleepTrackerViewModel(
158135
* recording.
159136
*/
160137
private suspend fun getTonightFromDatabase(): SleepNight? {
161-
return withContext(Dispatchers.IO) {
162-
var night = database.getTonight()
163-
if (night?.endTimeMilli != night?.startTimeMilli) {
164-
night = null
165-
}
166-
night
138+
var night = database.getTonight()
139+
if (night?.endTimeMilli != night?.startTimeMilli) {
140+
night = null
167141
}
142+
return night
168143
}
169144

170145
private suspend fun insert(night: SleepNight) {
171-
withContext(Dispatchers.IO) {
172-
database.insert(night)
173-
}
146+
database.insert(night)
174147
}
175148

176149
private suspend fun update(night: SleepNight) {
177-
withContext(Dispatchers.IO) {
178-
database.update(night)
179-
}
150+
database.update(night)
180151
}
181152

182153
private suspend fun clear() {
183-
withContext(Dispatchers.IO) {
184-
database.clear()
185-
}
154+
database.clear()
186155
}
187156

188157
/**
189158
* Executes when the START button is clicked.
190159
*/
191160
fun onStart() {
192-
uiScope.launch {
161+
viewModelScope.launch {
193162
// Create a new night, which captures the current time,
194163
// and insert it into the database.
195164
val newNight = SleepNight()
@@ -204,7 +173,7 @@ class SleepTrackerViewModel(
204173
* Executes when the STOP button is clicked.
205174
*/
206175
fun onStop() {
207-
uiScope.launch {
176+
viewModelScope.launch {
208177
// In Kotlin, the return@label syntax is used for specifying which function among
209178
// several nested ones this statement returns from.
210179
// In this case, we are specifying to return from launch().
@@ -224,7 +193,7 @@ class SleepTrackerViewModel(
224193
* Executes when the CLEAR button is clicked.
225194
*/
226195
fun onClear() {
227-
uiScope.launch {
196+
viewModelScope.launch {
228197
// Clear the database table.
229198
clear()
230199

@@ -235,15 +204,4 @@ class SleepTrackerViewModel(
235204
_showSnackbarEvent.value = true
236205
}
237206
}
238-
239-
/**
240-
* Called when the ViewModel is dismantled.
241-
* At this point, we want to cancel all coroutines;
242-
* otherwise we end up with processes that have nowhere to return to
243-
* using memory and resources.
244-
*/
245-
override fun onCleared() {
246-
super.onCleared()
247-
viewModelJob.cancel()
248-
}
249207
}

‎RecyclerViewGridLayout-Starter/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ buildscript {
2121
ext {
2222
kotlin_version = '1.3.72'
2323
archLifecycleVersion = '1.1.1'
24-
room_version = '2.2.0'
24+
room_version = '2.2.5'
2525
coroutine_version = '1.0.0'
2626
gradleVersion = '4.0.1'
2727
navigationVersion = '1.0.0-alpha07'

‎TrackMySleepQuality-Starter/app/build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ dependencies {
6363
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
6464
kapt "androidx.room:room-compiler:$room_version"
6565
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
66+
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
67+
68+
// Kotlin Extensions and Coroutines support for Room
69+
implementation "androidx.room:room-ktx:$room_version"
6670

6771
// Coroutines
6872
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
@@ -71,5 +75,10 @@ dependencies {
7175
// Navigation
7276
implementation "android.arch.navigation:navigation-fragment-ktx:$navigationVersion"
7377
implementation "android.arch.navigation:navigation-ui-ktx:$navigationVersion"
78+
79+
// Testing
80+
testImplementation 'junit:junit:4.12'
81+
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
82+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
7483
}
7584

‎TrackMySleepQualityCoroutines-Starter/app/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ dependencies {
6363
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
6464
kapt "androidx.room:room-compiler:$room_version"
6565
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
66+
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
67+
68+
// Kotlin Extensions and Coroutines support for Room
69+
implementation "androidx.room:room-ktx:$room_version"
6670

6771
// Coroutines
6872
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"

‎TrackMySleepQualityStates-Starter/app/build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ dependencies {
6363
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
6464
kapt "androidx.room:room-compiler:$room_version"
6565
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
66+
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
67+
68+
// Kotlin Extensions and Coroutines support for Room
69+
implementation "androidx.room:room-ktx:$room_version"
6670

6771
// Coroutines
6872
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
@@ -71,5 +75,10 @@ dependencies {
7175
// Navigation
7276
implementation "android.arch.navigation:navigation-fragment-ktx:$navigationVersion"
7377
implementation "android.arch.navigation:navigation-ui-ktx:$navigationVersion"
78+
79+
// Testing
80+
testImplementation 'junit:junit:4.12'
81+
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
82+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
7483
}
7584

‎TrackMySleepQualityStates-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,19 @@ import androidx.room.Update
2626
interface SleepDatabaseDao {
2727

2828
@Insert
29-
fun insert(night: SleepNight)
29+
suspend fun insert(night: SleepNight)
3030

3131
@Update
32-
fun update(night: SleepNight)
32+
suspend fun update(night: SleepNight)
3333

3434
@Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
35-
fun get(key: Long): SleepNight?
35+
suspend fun get(key: Long): SleepNight?
3636

3737
@Query("DELETE FROM daily_sleep_quality_table")
38-
fun clear()
38+
suspend fun clear()
3939

4040
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
41-
fun getTonight(): SleepNight?
41+
suspend fun getTonight(): SleepNight?
4242

4343
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
4444
fun getAllNights(): LiveData<List<SleepNight>>

‎TrackMySleepQualityStates-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import android.app.Application
2020
import androidx.lifecycle.AndroidViewModel
2121
import androidx.lifecycle.MutableLiveData
2222
import androidx.lifecycle.Transformations
23+
import androidx.lifecycle.viewModelScope
2324
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
2425
import com.example.android.trackmysleepquality.database.SleepNight
2526
import com.example.android.trackmysleepquality.formatNights
@@ -36,11 +37,6 @@ class SleepTrackerViewModel(
3637
val database: SleepDatabaseDao,
3738
application: Application) : AndroidViewModel(application) {
3839

39-
private var viewModelJob = Job()
40-
41-
42-
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
43-
4440
private val nights = database.getAllNights()
4541

4642
val nightsString = Transformations.map(nights) { nights ->
@@ -54,67 +50,54 @@ class SleepTrackerViewModel(
5450
}
5551

5652
private fun initializeTonight() {
57-
uiScope.launch {
53+
viewModelScope.launch {
5854
tonight.value = getTonightFromDatabase()
5955
}
6056
}
6157

6258
private suspend fun getTonightFromDatabase(): SleepNight? {
63-
64-
return withContext(Dispatchers.IO) {
65-
66-
var night = database.getTonight()
67-
if (night?.endTimeMilli != night?.startTimeMilli) {
68-
night = null
69-
}
70-
night
59+
var night = database.getTonight()
60+
if (night?.endTimeMilli != night?.startTimeMilli) {
61+
night = null
7162
}
63+
return night
7264
}
7365

66+
7467
fun onStartTracking() {
75-
uiScope.launch {
68+
viewModelScope.launch {
7669
val newNight = SleepNight()
7770
insert(newNight)
7871
tonight.value = getTonightFromDatabase()
7972
}
8073
}
8174

8275
private suspend fun insert(night: SleepNight) {
83-
withContext(Dispatchers.IO) {
84-
database.insert(night)
85-
}
76+
database.insert(night)
77+
8678
}
8779

8880
fun onStopTracking() {
89-
uiScope.launch {
81+
viewModelScope.launch {
9082
val oldNight = tonight.value ?: return@launch
9183
oldNight.endTimeMilli = System.currentTimeMillis()
9284
update(oldNight)
9385
}
9486
}
9587

9688
private suspend fun update(night: SleepNight) {
97-
withContext(Dispatchers.IO) {
98-
database.update(night)
99-
}
89+
database.update(night)
10090
}
10191

10292
fun onClear() {
103-
uiScope.launch {
93+
viewModelScope.launch {
10494
clear()
10595
tonight.value = null
10696
}
10797
}
10898

10999
suspend fun clear() {
110-
withContext(Dispatchers.IO) {
111-
database.clear()
112-
}
113-
}
114-
115-
override fun onCleared() {
116-
super.onCleared()
117-
viewModelJob.cancel()
100+
database.clear()
118101
}
119102
}
120103

0 commit comments

Comments
 (0)
This repository has been archived.