Skip to content

Instantly share code, notes, and snippets.

@kazukinr
Last active December 26, 2022 11:43
Show Gist options
  • Save kazukinr/c41430ec2399fba3c13b714df5aac489 to your computer and use it in GitHub Desktop.
Save kazukinr/c41430ec2399fba3c13b714df5aac489 to your computer and use it in GitHub Desktop.
A sample to manage CoroutinesScope with android lifecycle for ViewModel.
package com.github.kazukinr.android.ui.sample
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import java.io.Closeable
import kotlin.coroutines.CoroutineContext
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
interface LifecycleScopeObserver : DefaultLifecycleObserver {
val createdScope: CoroutineScope
val startedScope: CoroutineScope
val resumedScope: CoroutineScope
}
interface WithLifecycleScope {
val lifecycleScope: LifecycleScopeObserver
fun ViewModel.createdScope(): CoroutineScope =
lifecycleScope.createdScope
fun ViewModel.startedScope(): CoroutineScope =
lifecycleScope.startedScope
fun ViewModel.resumedScope(): CoroutineScope =
lifecycleScope.resumedScope
fun onCreate() {}
fun onStart() {}
fun onResume() {}
fun onPause() {}
fun onStop() {}
fun onDestroy() {}
}
private class LifecycleScopeObserverImpl(private val withLifecycleScope: WithLifecycleScope) : LifecycleScopeObserver {
private var created: CloseableCoroutineScope? = null
private var started: CloseableCoroutineScope? = null
private var resumed: CloseableCoroutineScope? = null
private var isCreated = false
private var isStarted = false
private var isResumed = false
override val createdScope: CoroutineScope
get() = created
?: if (isCreated) {
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main)
.also { created = it }
} else {
throw IllegalStateException("This operation is allowed only after created.")
}
override val startedScope: CoroutineScope
get() = started
?: if (isStarted) {
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main)
.also { started = it }
} else {
throw IllegalStateException("This operation is allowed only after started.")
}
override val resumedScope: CoroutineScope
get() = resumed
?: if (isResumed) {
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main)
.also { resumed = it }
} else {
throw IllegalStateException("This operation is allowed only after resumed.")
}
override fun onCreate(owner: LifecycleOwner) {
isCreated = true
withLifecycleScope.onCreate()
}
override fun onStart(owner: LifecycleOwner) {
isStarted = true
withLifecycleScope.onStart()
}
override fun onResume(owner: LifecycleOwner) {
isResumed = true
withLifecycleScope.onResume()
}
override fun onPause(owner: LifecycleOwner) {
isResumed = false
resumed?.close()
resumed = null
withLifecycleScope.onPause()
}
override fun onStop(owner: LifecycleOwner) {
isStarted = false
started?.close()
started = null
withLifecycleScope.onStop()
}
override fun onDestroy(owner: LifecycleOwner) {
isCreated = false
created?.close()
created = null
withLifecycleScope.onDestroy()
}
}
fun <T> lifecycleScope(): ReadOnlyProperty<T, LifecycleScopeObserver> where T : ViewModel, T : WithLifecycleScope =
object : ReadOnlyProperty<T, LifecycleScopeObserver> {
private var scopeObserver: LifecycleScopeObserver? = null
override fun getValue(thisRef: T, property: KProperty<*>): LifecycleScopeObserver =
scopeObserver ?: LifecycleScopeObserverImpl(thisRef).also { scopeObserver = it }
}
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment