-
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #121 from Dmitriy1892/lifecycle-coroutines
Lifecycle-aware extensions for kotlin coroutines and flows were added.
- Loading branch information
Showing
9 changed files
with
433 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
public final class com/arkivanov/essenty/lifecycle/coroutines/FlowWithLifecycleKt { | ||
public static final fun flowWithLifecycle (Lkotlinx/coroutines/flow/Flow;Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow; | ||
public static synthetic fun flowWithLifecycle$default (Lkotlinx/coroutines/flow/Flow;Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; | ||
public static final fun withLifecycle (Lkotlinx/coroutines/flow/Flow;Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow; | ||
public static synthetic fun withLifecycle$default (Lkotlinx/coroutines/flow/Flow;Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; | ||
} | ||
|
||
public final class com/arkivanov/essenty/lifecycle/coroutines/RepeatOnLifecycleKt { | ||
public static final fun repeatOnLifecycle (Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; | ||
public static synthetic fun repeatOnLifecycle$default (Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
public final class com/arkivanov/essenty/lifecycle/coroutines/FlowWithLifecycleKt { | ||
public static final fun flowWithLifecycle (Lkotlinx/coroutines/flow/Flow;Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow; | ||
public static synthetic fun flowWithLifecycle$default (Lkotlinx/coroutines/flow/Flow;Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; | ||
public static final fun withLifecycle (Lkotlinx/coroutines/flow/Flow;Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow; | ||
public static synthetic fun withLifecycle$default (Lkotlinx/coroutines/flow/Flow;Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; | ||
} | ||
|
||
public final class com/arkivanov/essenty/lifecycle/coroutines/RepeatOnLifecycleKt { | ||
public static final fun repeatOnLifecycle (Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; | ||
public static synthetic fun repeatOnLifecycle$default (Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import com.arkivanov.gradle.bundle | ||
import com.arkivanov.gradle.setupBinaryCompatibilityValidator | ||
import com.arkivanov.gradle.setupMultiplatform | ||
import com.arkivanov.gradle.setupPublication | ||
import com.arkivanov.gradle.setupSourceSets | ||
|
||
plugins { | ||
id("kotlin-multiplatform") | ||
id("com.android.library") | ||
id("com.arkivanov.gradle.setup") | ||
} | ||
|
||
setupMultiplatform() | ||
setupPublication() | ||
setupBinaryCompatibilityValidator() | ||
|
||
android { | ||
namespace = "com.arkivanov.essenty.lifecycle.coroutines" | ||
} | ||
|
||
kotlin { | ||
setupSourceSets { | ||
val android by bundle() | ||
|
||
common.main.dependencies { | ||
implementation(project(":utils-internal")) | ||
implementation(project(":lifecycle")) | ||
implementation(deps.kotlinx.coroutinesCore) | ||
} | ||
|
||
common.test.dependencies { | ||
implementation(deps.kotlinx.coroutinesTest) | ||
} | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
...nes/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/coroutines/FlowWithLifecycle.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package com.arkivanov.essenty.lifecycle.coroutines | ||
|
||
import com.arkivanov.essenty.lifecycle.Lifecycle | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.callbackFlow | ||
import kotlin.coroutines.CoroutineContext | ||
|
||
/** | ||
* Start [Flow] collecting when [Lifecycle.State] is at least as [minActiveState]. | ||
* It stopped collecting if "opposite" [Lifecycle.State] will appear. | ||
*/ | ||
fun <T> Flow<T>.withLifecycle( | ||
lifecycle: Lifecycle, | ||
minActiveState: Lifecycle.State = Lifecycle.State.STARTED, | ||
context: CoroutineContext = Dispatchers.Main | ||
): Flow<T> = callbackFlow { | ||
lifecycle.repeatOnLifecycle(minActiveState, context) { | ||
this@withLifecycle.collect { | ||
send(it) | ||
} | ||
} | ||
close() | ||
} | ||
|
||
@Deprecated( | ||
message = "Use 'withLifecycle' instead", | ||
replaceWith = ReplaceWith("withLifecycle(lifecycle, minActiveState)"), | ||
level = DeprecationLevel.ERROR | ||
) | ||
fun <T> Flow<T>.flowWithLifecycle( | ||
lifecycle: Lifecycle, | ||
minActiveState: Lifecycle.State = Lifecycle.State.STARTED, | ||
context: CoroutineContext = Dispatchers.Main | ||
): Flow<T> { | ||
return withLifecycle(lifecycle, minActiveState, context) | ||
} |
132 changes: 132 additions & 0 deletions
132
...nes/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/coroutines/RepeatOnLifecycle.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package com.arkivanov.essenty.lifecycle.coroutines | ||
|
||
import com.arkivanov.essenty.lifecycle.Lifecycle | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.Job | ||
import kotlinx.coroutines.coroutineScope | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.suspendCancellableCoroutine | ||
import kotlinx.coroutines.sync.Mutex | ||
import kotlinx.coroutines.sync.withLock | ||
import kotlinx.coroutines.withContext | ||
import kotlin.coroutines.CoroutineContext | ||
import kotlin.coroutines.resume | ||
|
||
/** | ||
* Repeat invocation [block] every time that passed [minActiveState] appears. | ||
* Work of passed [block] finished when "opposite" [Lifecycle.State] will appear. | ||
* | ||
* Note: This function works like a terminal operator and must be called in assembly coroutine. | ||
*/ | ||
suspend fun Lifecycle.repeatOnLifecycle( | ||
minActiveState: Lifecycle.State = Lifecycle.State.STARTED, | ||
context: CoroutineContext = Dispatchers.Main, | ||
block: suspend CoroutineScope.() -> Unit | ||
) { | ||
require(minActiveState != Lifecycle.State.INITIALIZED) { | ||
"repeatOnEssentyLifecycle cannot start work with the INITIALIZED lifecycle state." | ||
} | ||
|
||
if (this.state == Lifecycle.State.DESTROYED) { | ||
return | ||
} | ||
|
||
coroutineScope { | ||
withContext(context) { | ||
if (this@repeatOnLifecycle.state == Lifecycle.State.DESTROYED) { | ||
return@withContext | ||
} | ||
|
||
var callback: Lifecycle.Callbacks? = null | ||
var job: Job? = null | ||
val mutex = Mutex() | ||
|
||
try { | ||
suspendCancellableCoroutine { cont -> | ||
callback = createLifecycleAwareCallback( | ||
startState = minActiveState, | ||
onStateAppear = { | ||
job = launch { | ||
mutex.withLock { | ||
block() | ||
} | ||
} | ||
}, | ||
onStateDisappear = { | ||
job?.cancel() | ||
job = null | ||
}, | ||
onDestroy = { | ||
cont.resume(Unit) | ||
}, | ||
) | ||
|
||
this@repeatOnLifecycle.subscribe(requireNotNull(callback)) | ||
} | ||
} finally { | ||
job?.cancel() | ||
job = null | ||
callback?.let { | ||
this@repeatOnLifecycle.unsubscribe(it) | ||
} | ||
callback = null | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Creates lifecycle aware [Lifecycle.Callbacks] interface instance. | ||
* | ||
* @param startState [Lifecycle.State] that [onStateAppear] block must be called from | ||
* @param onStateAppear block of code that will be executed when the [Lifecycle.State] was equal [startState] | ||
* @param onStateDisappear block of code that will be executed when the [Lifecycle.State] was equal to opposite [startState] | ||
* @param onDestroy block of code that will be executed when the [Lifecycle.State] was equal [Lifecycle.State.DESTROYED] | ||
* | ||
* @return [Lifecycle.Callbacks] | ||
*/ | ||
private fun createLifecycleAwareCallback( | ||
startState: Lifecycle.State, | ||
onStateAppear: () -> Unit, | ||
onStateDisappear: () -> Unit, | ||
onDestroy: () -> Unit, | ||
): Lifecycle.Callbacks = object : Lifecycle.Callbacks { | ||
|
||
override fun onCreate() { | ||
launchIfState(Lifecycle.State.CREATED) | ||
} | ||
|
||
override fun onStart() { | ||
launchIfState(Lifecycle.State.STARTED) | ||
} | ||
|
||
override fun onResume() { | ||
launchIfState(Lifecycle.State.RESUMED) | ||
} | ||
|
||
override fun onPause() { | ||
closeIfState(Lifecycle.State.RESUMED) | ||
} | ||
|
||
override fun onStop() { | ||
closeIfState(Lifecycle.State.STARTED) | ||
} | ||
|
||
override fun onDestroy() { | ||
closeIfState(Lifecycle.State.CREATED) | ||
onDestroy() | ||
} | ||
|
||
private fun launchIfState(state: Lifecycle.State) { | ||
if (startState == state) { | ||
onStateAppear() | ||
} | ||
} | ||
|
||
private fun closeIfState(state: Lifecycle.State) { | ||
if (startState == state) { | ||
onStateDisappear() | ||
} | ||
} | ||
} |
Oops, something went wrong.