Skip to content

Commit

Permalink
Use ViewmodelScope instead of creating coroutine scope
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
android-dev-lxl committed Aug 26, 2020
1 parent 54e9b62 commit 8ba55c6
Show file tree
Hide file tree
Showing 41 changed files with 165 additions and 564 deletions.
5 changes: 3 additions & 2 deletions DevBytesRepository/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ android {
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
}
buildTypes {
release {
Expand All @@ -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'
Expand Down Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -42,7 +43,6 @@ import java.io.IOException
*/
class DevByteViewModel(application: Application) : AndroidViewModel(application) {


/**
* The data source this ViewModel will fetch results from.
*/
Expand All @@ -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.
Expand Down Expand Up @@ -120,23 +105,13 @@ class DevByteViewModel(application: Application) : AndroidViewModel(application)
}
}


/**
* Resets the network error flag.
*/
fun onNetworkErrorShown() {
_isNetworkErrorShown.value = true
}


/**
* Cancel all coroutines when the ViewModel is cleared
*/
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}

/**
* Factory for constructing DevByteViewModel with parameter
*/
Expand Down
9 changes: 5 additions & 4 deletions DevBytesWorkManager/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ android {
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
}
buildTypes {
release {
Expand All @@ -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'
Expand Down Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
*/
Expand Down
4 changes: 4 additions & 0 deletions RecyclerViewClickHandler/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -40,23 +40,23 @@ 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.
*
* @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,
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<SleepNight>

fun getNight() = night
Expand All @@ -72,16 +65,6 @@ class SleepDetailViewModel(
val navigateToSleepTracker: LiveData<Boolean?>
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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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].
*
Expand All @@ -73,16 +51,6 @@ class SleepQualityViewModel(
val navigateToSleepTracker: LiveData<Boolean?>
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]
*/
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down
Loading

0 comments on commit 8ba55c6

Please sign in to comment.