Skip to content

Commit

Permalink
implement new subscription lifecycle logic that can be provided from …
Browse files Browse the repository at this point in the history
…outside
  • Loading branch information
Nek-12 committed Mar 8, 2024
1 parent fef5f75 commit df86e8e
Show file tree
Hide file tree
Showing 17 changed files with 146 additions and 297 deletions.
2 changes: 1 addition & 1 deletion compose/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ kotlin {
androidMain.dependencies {
implementation(libs.compose.foundation)
implementation(libs.compose.preview)
implementation(libs.compose.lifecycle.viewmodel)
implementation(libs.compose.lifecycle.runtime)
api(projects.android)
}
Expand All @@ -39,6 +38,7 @@ kotlin {
}
jvmMain.dependencies {
implementation(compose.desktop.common)
implementation(libs.compose.lifecycle.runtime)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package pro.respawn.flowmvi.compose.android

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
import pro.respawn.flowmvi.compose.api.SubscriberLifecycleOwner
import pro.respawn.flowmvi.compose.api.SubscriptionMode

public fun LifecycleOwner.asSubscriberOwner(): SubscriberLifecycleOwner = SubscriberLifecycleOwner { mode, block ->

Check warning on line 9 in compose/src/androidMain/kotlin/pro/respawn/flowmvi/compose/android/AndroidInterop.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

compose/src/androidMain/kotlin/pro/respawn/flowmvi/compose/android/AndroidInterop.kt#L9

The function asSubscriberOwner is missing documentation.
repeatOnLifecycle(mode.asLifecycleState, block)
}

public val SubscriptionMode.asLifecycleState: Lifecycle.State

Check warning on line 13 in compose/src/androidMain/kotlin/pro/respawn/flowmvi/compose/android/AndroidInterop.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

compose/src/androidMain/kotlin/pro/respawn/flowmvi/compose/android/AndroidInterop.kt#L13

The property asLifecycleState is missing documentation.
get() = when (this) {
SubscriptionMode.Immediate -> Lifecycle.State.CREATED
SubscriptionMode.Started -> Lifecycle.State.STARTED
SubscriptionMode.Visible -> Lifecycle.State.RESUMED
}

public val Lifecycle.State.asSubscriptionMode: SubscriptionMode

Check warning on line 20 in compose/src/androidMain/kotlin/pro/respawn/flowmvi/compose/android/AndroidInterop.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

compose/src/androidMain/kotlin/pro/respawn/flowmvi/compose/android/AndroidInterop.kt#L20

The property asSubscriptionMode is missing documentation.
get() = when (this) {
Lifecycle.State.CREATED -> SubscriptionMode.Immediate
Lifecycle.State.STARTED -> SubscriptionMode.Started
Lifecycle.State.RESUMED -> SubscriptionMode.Visible
Lifecycle.State.DESTROYED,
Lifecycle.State.INITIALIZED -> error("Android lifecycle does not support $this as subscription mode")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package pro.respawn.flowmvi.compose.dsl

import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalLifecycleOwner
import pro.respawn.flowmvi.compose.android.asSubscriberOwner
import pro.respawn.flowmvi.compose.api.SubscriberLifecycleOwner

internal actual val PlatformLifecycle: SubscriberLifecycleOwner?
@Composable get() = LocalLifecycleOwner.current.asSubscriberOwner()
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,19 @@
package pro.respawn.flowmvi.compose.dsl

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pro.respawn.flowmvi.android.subscribe
import pro.respawn.flowmvi.api.DelicateStoreApi
import pro.respawn.flowmvi.api.FlowMVIDSL
import pro.respawn.flowmvi.api.ImmutableStore
import pro.respawn.flowmvi.api.MVIAction
import pro.respawn.flowmvi.api.MVIIntent
import pro.respawn.flowmvi.api.MVIState
import pro.respawn.flowmvi.compose.android.asSubscriberOwner
import pro.respawn.flowmvi.compose.android.asSubscriptionMode
import pro.respawn.flowmvi.dsl.subscribe

/**
Expand All @@ -39,30 +33,14 @@ import pro.respawn.flowmvi.dsl.subscribe
* @see ImmutableStore.subscribe
* @see subscribe
*/
@OptIn(DelicateStoreApi::class)
@Suppress("NOTHING_TO_INLINE", "ComposableParametersOrdering")
@Composable
@FlowMVIDSL
public inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribe(
lifecycleState: Lifecycle.State = Lifecycle.State.STARTED,
lifecycleState: Lifecycle.State,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
noinline consume: suspend CoroutineScope.(action: A) -> Unit,
): State<S> {
val owner = LocalLifecycleOwner.current
val state = remember(this) { mutableStateOf(state) }
val block by rememberUpdatedState(consume)
LaunchedEffect(this@subscribe, lifecycleState, owner) {
withContext(Dispatchers.Main.immediate) {
owner.repeatOnLifecycle(lifecycleState) {
subscribe(
store = this@subscribe,
consume = { block(it) },
render = { state.value = it }
).join()
}
}
}
return state
}
): State<S> = subscribe(lifecycleState.asSubscriptionMode, lifecycleOwner.asSubscriberOwner(), consume)

/**
* A function to subscribe to the store that follows the system lifecycle.
Expand All @@ -77,42 +55,10 @@ public inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S,
* @see ImmutableStore.subscribe
* @see subscribe
*/
@OptIn(DelicateStoreApi::class)
@Suppress("NOTHING_TO_INLINE")
@Composable
@FlowMVIDSL
public inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribe(
lifecycleState: Lifecycle.State = Lifecycle.State.STARTED,
): State<S> {
val owner = LocalLifecycleOwner.current
val state = remember(this) { mutableStateOf(state) }
LaunchedEffect(this@subscribe, lifecycleState, owner) {
withContext(Dispatchers.Main.immediate) {
owner.repeatOnLifecycle(lifecycleState) {
subscribe(
store = this@subscribe,
render = { state.value = it }
).join()
}
}
}
return state
}

/**
* Alias for [pro.respawn.flowmvi.compose.dsl.subscribe] with [Lifecycle.State.STARTED].
**/
@FlowMVIDSL
@Composable
public actual inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribe(): State<S> =
subscribe(Lifecycle.State.STARTED)

/**
* Alias for [pro.respawn.flowmvi.compose.dsl.subscribe] with [Lifecycle.State.STARTED].
**/
@FlowMVIDSL
@Composable
@Suppress("NOTHING_TO_INLINE", "ComposableParametersOrdering")
public actual inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribe(
noinline consume: suspend CoroutineScope.(action: A) -> Unit
): State<S> = subscribe(Lifecycle.State.STARTED, consume)
lifecycleState: Lifecycle.State,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
): State<S> = subscribe(lifecycleState.asSubscriptionMode, lifecycleOwner.asSubscriberOwner())
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package pro.respawn.flowmvi.compose.api

import kotlinx.coroutines.CoroutineScope

public fun interface SubscriberLifecycleOwner {

Check warning on line 5 in compose/src/commonMain/kotlin/pro/respawn/flowmvi/compose/api/SubscriberLifecycleOwner.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

compose/src/commonMain/kotlin/pro/respawn/flowmvi/compose/api/SubscriberLifecycleOwner.kt#L5

SubscriberLifecycleOwner is missing required documentation.

public suspend fun repeatOnLifecycle(mode: SubscriptionMode, block: suspend CoroutineScope.() -> Unit)

Check warning on line 7 in compose/src/commonMain/kotlin/pro/respawn/flowmvi/compose/api/SubscriberLifecycleOwner.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

compose/src/commonMain/kotlin/pro/respawn/flowmvi/compose/api/SubscriberLifecycleOwner.kt#L7

The function repeatOnLifecycle is missing documentation.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pro.respawn.flowmvi.compose.api

public enum class SubscriptionMode {
Immediate,
Started,
Visible,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package pro.respawn.flowmvi.compose.dsl

import kotlinx.coroutines.coroutineScope
import pro.respawn.flowmvi.compose.api.SubscriberLifecycleOwner

@PublishedApi
internal val ImmediateLifecycleOwner: SubscriberLifecycleOwner = SubscriberLifecycleOwner { _, block ->
coroutineScope(block)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package pro.respawn.flowmvi.compose.dsl

import androidx.compose.runtime.Composable
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.staticCompositionLocalOf
import pro.respawn.flowmvi.compose.api.SubscriberLifecycleOwner

public val LocalSubscriberLifecycle: ProvidableCompositionLocal<SubscriberLifecycleOwner?> = staticCompositionLocalOf {

Check warning on line 8 in compose/src/commonMain/kotlin/pro/respawn/flowmvi/compose/dsl/LocalSubscriberLifecycle.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

compose/src/commonMain/kotlin/pro/respawn/flowmvi/compose/dsl/LocalSubscriberLifecycle.kt#L8

The property LocalSubscriberLifecycle is missing documentation.
null
}

@PublishedApi
internal val CurrentLifecycle: SubscriberLifecycleOwner
@Composable get() = LocalSubscriberLifecycle.current ?: PlatformLifecycle ?: ImmediateLifecycleOwner

@get:Composable
internal expect val PlatformLifecycle: SubscriberLifecycleOwner?
Original file line number Diff line number Diff line change
Expand Up @@ -8,78 +8,62 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pro.respawn.flowmvi.api.DelicateStoreApi
import pro.respawn.flowmvi.api.FlowMVIDSL
import pro.respawn.flowmvi.api.ImmutableStore
import pro.respawn.flowmvi.api.MVIAction
import pro.respawn.flowmvi.api.MVIIntent
import pro.respawn.flowmvi.api.MVIState
import pro.respawn.flowmvi.api.Store
import pro.respawn.flowmvi.compose.api.SubscriberLifecycleOwner
import pro.respawn.flowmvi.compose.api.SubscriptionMode
import pro.respawn.flowmvi.dsl.subscribe
import pro.respawn.flowmvi.util.immediateOrDefault

/**
* A function to subscribe to the store that follows the system lifecycle.
*
* * This function will assign the store a new subscriber when invoked, then populate the returned [State] with new states.
* * Provided [consume] parameter will be used to consume actions that come from the store.
* * Store's subscribers will **not** wait until the store is launched when they subscribe to the store.
* Such subscribers will not receive state updates or actions. Don't forget to launch the store.
*
* @param consume a lambda to consume actions with.
* @return the [State] that contains the [Store.state].
* @see Store.subscribe
*/
@Composable
@FlowMVIDSL
public expect inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribe(
noinline consume: suspend CoroutineScope.(action: A) -> Unit,
): State<S>

/**
* A function to subscribe to the store that follows the system lifecycle.
*
* * This function will not collect [MVIAction]s.
* * This function will assign the store a new subscriber when invoked, then populate the returned [State] with new states.
* * Store's subscribers will **not** wait until the store is launched when they subscribe to the store.
* Such subscribers will not receive state updates or actions. Don't forget to launch the store.
* upon leaving that state, the function will unsubscribe.
* @return the [State] that contains the [Store.state].
* @see Store.subscribe
*/
@Composable
@FlowMVIDSL
public expect inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribe(): State<S>

@PublishedApi
@OptIn(DelicateStoreApi::class)
@Suppress("ComposableParametersOrdering")
@Composable
@FlowMVIDSL
internal inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribeDirect(): State<S> {
public inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribe(

Check warning on line 28 in compose/src/commonMain/kotlin/pro/respawn/flowmvi/compose/dsl/SubscribeDsl.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

compose/src/commonMain/kotlin/pro/respawn/flowmvi/compose/dsl/SubscribeDsl.kt#L28

The function subscribe is missing documentation.
mode: SubscriptionMode = SubscriptionMode.Started,
owner: SubscriberLifecycleOwner = CurrentLifecycle,
noinline consume: suspend CoroutineScope.(action: A) -> Unit,
): State<S> {
val state = remember(this) { mutableStateOf(state) }
LaunchedEffect(this@subscribeDirect) {
subscribe(
store = this@subscribeDirect,
render = { state.value = it },
).join()
val block by rememberUpdatedState(consume)
LaunchedEffect(this@subscribe, mode, owner) {
withContext(Dispatchers.Main.immediateOrDefault) {
owner.repeatOnLifecycle(mode) {
subscribe(
store = this@subscribe,
consume = { block(it) },
render = { state.value = it }
).join()
}
}
}
return state
}

@PublishedApi

Check notice on line 49 in compose/src/commonMain/kotlin/pro/respawn/flowmvi/compose/dsl/SubscribeDsl.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

compose/src/commonMain/kotlin/pro/respawn/flowmvi/compose/dsl/SubscribeDsl.kt#L49

Needless blank line(s)
@OptIn(DelicateStoreApi::class)
@Composable
@FlowMVIDSL
internal inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribeDirect(
noinline consume: suspend CoroutineScope.(action: A) -> Unit,
public inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribe(
mode: SubscriptionMode = SubscriptionMode.Visible,
owner: SubscriberLifecycleOwner = CurrentLifecycle,
): State<S> {
val state = remember(this) { mutableStateOf(state) }
val block by rememberUpdatedState(consume)
LaunchedEffect(this@subscribeDirect) {
subscribe(
store = this@subscribeDirect,
render = { state.value = it },
consume = { block(it) }
).join()
LaunchedEffect(this@subscribe, mode, owner) {
withContext(Dispatchers.Main.immediateOrDefault) {
owner.repeatOnLifecycle(mode) {
subscribe(
store = this@subscribe,
render = { state.value = it }
).join()
}
}
}
return state
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package pro.respawn.flowmvi.compose.dsl

import androidx.compose.runtime.Composable
import pro.respawn.flowmvi.compose.api.SubscriberLifecycleOwner

internal actual val PlatformLifecycle: SubscriberLifecycleOwner? @Composable get() = null

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package pro.respawn.flowmvi.compose.dsl

import androidx.compose.runtime.Composable
import pro.respawn.flowmvi.compose.api.SubscriberLifecycleOwner

internal actual val PlatformLifecycle: SubscriberLifecycleOwner? @Composable get() = null
Loading

0 comments on commit df86e8e

Please sign in to comment.