Skip to content

Commit

Permalink
rename subscriber lifecycle owner to just lifecycle, implement essent…
Browse files Browse the repository at this point in the history
…y-compose interop
  • Loading branch information
Nek-12 committed Mar 8, 2024
1 parent 8536dba commit d07a5f6
Show file tree
Hide file tree
Showing 18 changed files with 208 additions and 46 deletions.
13 changes: 5 additions & 8 deletions compose/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ plugins {

android {
configureAndroidLibrary(this)
namespace = "${Config.namespace}.compose"

buildFeatures {
compose = true
}
}

kotlin {
Expand Down Expand Up @@ -43,14 +48,6 @@ kotlin {
}
}

android {
namespace = "${Config.namespace}.compose"

buildFeatures {
compose = true
}
}

dependencies {
debugImplementation(libs.compose.tooling)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ 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.SubscriberLifecycle
import pro.respawn.flowmvi.compose.api.SubscriptionMode

public fun LifecycleOwner.asSubscriberOwner(): SubscriberLifecycleOwner = SubscriberLifecycleOwner { mode, block ->
public fun LifecycleOwner.asSubscriberOwner(): SubscriberLifecycle = SubscriberLifecycle { 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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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
import pro.respawn.flowmvi.compose.api.SubscriberLifecycle

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

import kotlinx.coroutines.CoroutineScope

public fun interface SubscriberLifecycleOwner {
public fun interface SubscriberLifecycle {

public suspend fun repeatOnLifecycle(mode: SubscriptionMode, block: suspend CoroutineScope.() -> Unit)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package pro.respawn.flowmvi.compose.dsl

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import pro.respawn.flowmvi.compose.api.SubscriberLifecycle

@Composable
public fun ProvideSubscriberLifecycle(

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

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

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

The function ProvideSubscriberLifecycle is missing documentation.
lifecycleOwner: SubscriberLifecycle,
content: @Composable () -> Unit
): Unit = CompositionLocalProvider(
LocalSubscriberLifecycle provides lifecycleOwner,
content = content,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pro.respawn.flowmvi.compose.dsl

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

@PublishedApi
internal val ImmediateLifecycle: SubscriberLifecycle = SubscriberLifecycle { _, block -> coroutineScope(block) }

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ 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
import pro.respawn.flowmvi.compose.api.SubscriberLifecycle

public val LocalSubscriberLifecycle: ProvidableCompositionLocal<SubscriberLifecycleOwner?> = staticCompositionLocalOf {
public val LocalSubscriberLifecycle: ProvidableCompositionLocal<SubscriberLifecycle?> = 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?
internal expect val PlatformLifecycle: SubscriberLifecycle?

@PublishedApi
internal val CurrentLifecycle: SubscriberLifecycle
@Composable get() = LocalSubscriberLifecycle.current ?: PlatformLifecycle ?: ImmediateLifecycle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ 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.api.SubscriberLifecycleOwner
import pro.respawn.flowmvi.compose.api.SubscriberLifecycle
import pro.respawn.flowmvi.compose.api.SubscriptionMode
import pro.respawn.flowmvi.dsl.subscribe
import pro.respawn.flowmvi.util.immediateOrDefault
Expand All @@ -27,14 +27,14 @@ import pro.respawn.flowmvi.util.immediateOrDefault
@FlowMVIDSL
public inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribe(
mode: SubscriptionMode = SubscriptionMode.Started,
owner: SubscriberLifecycleOwner = CurrentLifecycle,
lifecycle: SubscriberLifecycle = CurrentLifecycle,
noinline consume: suspend CoroutineScope.(action: A) -> Unit,
): State<S> {
val state = remember(this) { mutableStateOf(state) }
val block by rememberUpdatedState(consume)
LaunchedEffect(this@subscribe, mode, owner) {
LaunchedEffect(this@subscribe, mode, lifecycle) {
withContext(Dispatchers.Main.immediateOrDefault) {
owner.repeatOnLifecycle(mode) {
lifecycle.repeatOnLifecycle(mode) {
subscribe(
store = this@subscribe,
consume = { block(it) },
Expand All @@ -51,13 +51,13 @@ public inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S,
@Composable
@FlowMVIDSL
public inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribe(
mode: SubscriptionMode = SubscriptionMode.Visible,
owner: SubscriberLifecycleOwner = CurrentLifecycle,
mode: SubscriptionMode = SubscriptionMode.Started,
lifecycle: SubscriberLifecycle = CurrentLifecycle,
): State<S> {
val state = remember(this) { mutableStateOf(state) }
LaunchedEffect(this@subscribe, mode, owner) {
LaunchedEffect(this@subscribe, mode, lifecycle) {
withContext(Dispatchers.Main.immediateOrDefault) {
owner.repeatOnLifecycle(mode) {
lifecycle.repeatOnLifecycle(mode) {
subscribe(
store = this@subscribe,
render = { state.value = it }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package pro.respawn.flowmvi.compose.dsl

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

internal actual val PlatformLifecycle: SubscriberLifecycleOwner? @Composable get() = null
internal actual val PlatformLifecycle: SubscriberLifecycle? @Composable get() = null
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package pro.respawn.flowmvi.compose.dsl

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

internal actual val PlatformLifecycle: SubscriberLifecycleOwner? @Composable get() = null
internal actual val PlatformLifecycle: SubscriberLifecycle? @Composable get() = null
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package pro.respawn.flowmvi.compose.dsl

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

internal actual val PlatformLifecycle: SubscriberLifecycleOwner? @Composable get() = null
internal actual val PlatformLifecycle: SubscriberLifecycle? @Composable get() = null
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package pro.respawn.flowmvi.compose.dsl

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

internal actual val PlatformLifecycle: SubscriberLifecycleOwner? @Composable get() = null
internal actual val PlatformLifecycle: SubscriberLifecycle? @Composable get() = null
54 changes: 54 additions & 0 deletions essenty/essenty-compose/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
plugins {

Check warning on line 1 in essenty/essenty-compose/build.gradle.kts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

essenty/essenty-compose/build.gradle.kts#L1

The file does not contain a package declaration.
id(libs.plugins.kotlinMultiplatform.id)
id(libs.plugins.androidLibrary.id)
alias(libs.plugins.jetbrainsCompose)
id("maven-publish")
signing
}

android {
configureAndroidLibrary(this)
namespace = "${Config.namespace}.decompose.compose"

buildFeatures {
compose = true
}
}

kotlin {
configureMultiplatform(
ext = this,
tvOs = false,
watchOs = false,
linux = false,
windows = false,
)
sourceSets {
androidMain.dependencies {
implementation(libs.compose.foundation)
implementation(libs.compose.preview)
implementation(libs.compose.lifecycle.runtime)
api(projects.android)
}
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
api(projects.core)
api(projects.compose)

api(libs.essenty.lifecycle)
api(libs.essenty.lifecycle.coroutines)
api(libs.essenty.instancekeeper)
}
jvmMain.dependencies {
implementation(compose.desktop.common)
implementation(libs.compose.lifecycle.runtime)
}
}
}

publishMultiplatform()

dependencies {

Check notice on line 53 in essenty/essenty-compose/build.gradle.kts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

essenty/essenty-compose/build.gradle.kts#L53

Unexpected blank line(s) before "}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package pro.respawn.flowmvi.decompose.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import com.arkivanov.essenty.lifecycle.LifecycleOwner
import pro.respawn.flowmvi.compose.dsl.LocalSubscriberLifecycle

@Composable
public fun ProvideSubscriberLifecycle(

Check warning on line 9 in essenty/essenty-compose/src/commonMain/kotlin/pro/respawn/flowmvi/decompose/compose/CompositionLocals.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

essenty/essenty-compose/src/commonMain/kotlin/pro/respawn/flowmvi/decompose/compose/CompositionLocals.kt#L9

The function ProvideSubscriberLifecycle is missing documentation.
owner: LifecycleOwner,
content: @Composable () -> Unit
): Unit = CompositionLocalProvider(
LocalSubscriberLifecycle provides owner.asSubscriberLifecycle,
content = content,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package pro.respawn.flowmvi.decompose.compose

import com.arkivanov.essenty.lifecycle.Lifecycle
import com.arkivanov.essenty.lifecycle.LifecycleOwner
import com.arkivanov.essenty.lifecycle.coroutines.repeatOnLifecycle
import pro.respawn.flowmvi.compose.api.SubscriberLifecycle
import pro.respawn.flowmvi.compose.api.SubscriptionMode

public val LifecycleOwner.asSubscriberLifecycle: SubscriberLifecycle

Check warning on line 9 in essenty/essenty-compose/src/commonMain/kotlin/pro/respawn/flowmvi/decompose/compose/Lifecycle.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

essenty/essenty-compose/src/commonMain/kotlin/pro/respawn/flowmvi/decompose/compose/Lifecycle.kt#L9

The property asSubscriberLifecycle is missing documentation.
get() = SubscriberLifecycle { mode, block -> repeatOnLifecycle(mode.asEssentyLifecycle, block = block) }

public val SubscriptionMode.asEssentyLifecycle: Lifecycle.State

Check warning on line 12 in essenty/essenty-compose/src/commonMain/kotlin/pro/respawn/flowmvi/decompose/compose/Lifecycle.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

essenty/essenty-compose/src/commonMain/kotlin/pro/respawn/flowmvi/decompose/compose/Lifecycle.kt#L12

The property asEssentyLifecycle 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
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("Essenty does not provide support for using $name as subscriber lifecycle")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package pro.respawn.flowmvi.decompose.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import com.arkivanov.essenty.lifecycle.Lifecycle
import com.arkivanov.essenty.lifecycle.LifecycleOwner
import kotlinx.coroutines.CoroutineScope
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.dsl.subscribe
import pro.respawn.flowmvi.dsl.subscribe

/**
* 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 lifecycleState the minimum lifecycle state that should be reached in order to subscribe to the store,
* upon leaving that state, the function will unsubscribe.
* @param consume a lambda to consume actions with.
* @return the [State] that contains the current state.
* @see ImmutableStore.subscribe
* @see subscribe
*/
@Suppress("ComposableParametersOrdering")
@Composable
@FlowMVIDSL
public inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribe(
lifecycleOwner: LifecycleOwner,
lifecycleState: Lifecycle.State = Lifecycle.State.STARTED,
noinline consume: suspend CoroutineScope.(action: A) -> Unit,
): State<S> = subscribe(lifecycleState.asSubscriptionMode, lifecycleOwner.asSubscriberLifecycle, consume)

/**
* 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.
* @param lifecycleState the minimum lifecycle state that should be reached in order to subscribe to the store,
* upon leaving that state, the function will unsubscribe.
* @return the [State] that contains the current state.
* @see ImmutableStore.subscribe
* @see subscribe
*/
@Composable
@FlowMVIDSL
public inline fun <S : MVIState, I : MVIIntent, A : MVIAction> ImmutableStore<S, I, A>.subscribe(
lifecycleOwner: LifecycleOwner,
lifecycleState: Lifecycle.State = Lifecycle.State.CREATED,
): State<S> = subscribe(lifecycleState.asSubscriptionMode, lifecycleOwner.asSubscriberLifecycle)
3 changes: 2 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ include(":android-compose")
include(":android-view")
include(":compose")
include(":savedstate")
include(":decompose")
include(":essenty")
include(":essenty:essenty-compose")
include(":debugger:app")
include(":debugger:debugger-client")
include(":debugger:debugger-plugin")
Expand Down

0 comments on commit d07a5f6

Please sign in to comment.