Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lifecycle stack entry #16

Merged
merged 15 commits into from
Jan 24, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 49 additions & 15 deletions detekt.yml
Original file line number Diff line number Diff line change
@@ -714,56 +714,90 @@ style:
- 'java.util.*'

Compose:
ComposableAnnotationNaming:
active: true
ComposableNaming:
active: true
# -- You can optionally disable the checks in this rule for regex matches against the composable name (e.g. molecule presenters)
# allowedComposableFunctionNames: .*Presenter,.*MoleculePresenter
ComposableParamOrder:
active: true
# -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically)
# treatAsLambda: MyLambdaType
CompositionLocalAllowlist:
active: true
# You can optionally define a list of CompositionLocals that are allowed here
allowedCompositionLocals: LocalViewModelStoreOwner
# -- You can optionally define a list of CompositionLocals that are allowed here
allowedCompositionLocals: LocalNavigationExecutor,LocalLifecycleOwner,LocalNavigator
CompositionLocalNaming:
active: true
ContentEmitterReturningValues:
active: true
# You can optionally add your own composables here
# -- You can optionally add your own composables here
# contentEmitters: MyComposable,MyOtherComposable
DefaultsVisibility:
active: true
LambdaParameterInRestartableEffect:
active: true
# -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically)
# treatAsLambda: MyLambdaType
ModifierClickableOrder:
active: true
# -- You can optionally add your own Modifier types
# customModifiers: BananaModifier,PotatoModifier
ModifierComposable:
active: true
# -- You can optionally add your own Modifier types
# customModifiers: BananaModifier,PotatoModifier
ModifierMissing:
active: true
# You can optionally control the visibility of which composables to check for here
# Possible values are: `only_public`, `public_and_internal` and `all` (default is `only_public`)
# -- You can optionally control the visibility of which composables to check for here
# -- Possible values are: `only_public`, `public_and_internal` and `all` (default is `only_public`)
# checkModifiersForVisibility: only_public
# -- You can optionally add your own Modifier types
# customModifiers: BananaModifier,PotatoModifier
ModifierNaming:
active: true
# -- You can optionally add your own Modifier types
# customModifiers: BananaModifier,PotatoModifier
ModifierNotUsedAtRoot:
active: true
# You can optionally add your own composables here
# -- You can optionally add your own composables here
# contentEmitters: MyComposable,MyOtherComposable
# -- You can optionally add your own Modifier types
# customModifiers: BananaModifier,PotatoModifier
ModifierReused:
active: true
# -- You can optionally add your own Modifier types
# customModifiers: BananaModifier,PotatoModifier
ModifierWithoutDefault:
active: true
MultipleEmitters:
active: true
# You can optionally add your own composables here
# -- You can optionally add your own composables here that will count as content emitters
# contentEmitters: MyComposable,MyOtherComposable
# -- You can add composables here that you don't want to count as content emitters (e.g. custom dialogs or modals)
# contentEmittersDenylist: MyNonEmitterComposable
MutableParams:
active: true
ComposableNaming:
MutableStateParam:
active: true
# You can optionally disable the checks in this rule for regex matches against the composable name (e.g. molecule presenters)
# allowedComposableFunctionNames: .*Presenter,.*MoleculePresenter
ComposableParamOrder:
active: true
PreviewNaming:
PreviewAnnotationNaming:
active: true
PreviewPublic:
active: true
RememberMissing:
active: true
RememberContentMissing:
active: true
UnstableCollections:
active: true
ViewModelForwarding:
active: true
# -- You can optionally use this rule on things other than types ending in "ViewModel" or "Presenter" (which are the defaults). You can add your own via a regex here:
# allowedStateHolderNames: .*ViewModel,.*Presenter
# -- You can optionally add an allowlist for Composable names that won't be affected by this rule
# allowedForwarding: .*Content,.*FancyStuff
ViewModelInjection:
active: true
# You can optionally add your own ViewModel factories here
viewModelFactories: hiltViewModel, koinViewModel, kmpViewModel
# -- You can optionally add your own ViewModel factories here
viewModelFactories: hiltViewModel,potatoViewModel,koinViewModel,kmpViewModel,koinKmpViewModel
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ koin = "3.5.3"
koin-compose = "1.1.2"
koin-androidx-compose = "3.5.3"
coil = "2.5.0"
compose-rules-detekt = "0.3.9"
compose-rules-detekt = "0.3.10"

androidx-lifecycle = "2.7.0"
androidx-annotation = "1.7.1"
Original file line number Diff line number Diff line change
@@ -132,7 +132,6 @@ internal suspend fun <R : Parcelable> NavigationExecutor.collectAndHandleNavigat
@Parcelize
private object InitialValue : Parcelable

@Suppress("CompositionLocalAllowlist")
@InternalNavigationApi
public val LocalNavigationExecutor: ProvidableCompositionLocal<NavigationExecutor> = staticCompositionLocalOf {
throw IllegalStateException("Can't use NavEventNavigationHandler outside of a navigator NavHost")
10 changes: 5 additions & 5 deletions lifecycle/api/android/lifecycle.api
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ public final class com/hoc081098/solivagant/lifecycle/FlowWithLifecycleKt {
}

public abstract interface class com/hoc081098/solivagant/lifecycle/Lifecycle {
public static final field Companion Lcom/hoc081098/solivagant/lifecycle/Lifecycle$Companion;
public abstract fun getCurrentState ()Lcom/hoc081098/solivagant/lifecycle/Lifecycle$State;
public abstract fun getCurrentStateFlow ()Lkotlinx/coroutines/flow/StateFlow;
public abstract fun subscribe (Lcom/hoc081098/solivagant/lifecycle/Lifecycle$Observer;)Lcom/hoc081098/solivagant/lifecycle/Lifecycle$Cancellable;
}
@@ -20,10 +20,6 @@ public abstract interface class com/hoc081098/solivagant/lifecycle/Lifecycle$Can
public abstract fun cancel ()V
}

public final class com/hoc081098/solivagant/lifecycle/Lifecycle$Companion {
public final fun getCurrentState (Lcom/hoc081098/solivagant/lifecycle/Lifecycle;)Lcom/hoc081098/solivagant/lifecycle/Lifecycle$State;
}

public final class com/hoc081098/solivagant/lifecycle/Lifecycle$Event : java/lang/Enum {
public static final field Companion Lcom/hoc081098/solivagant/lifecycle/Lifecycle$Event$Companion;
public static final field ON_CREATE Lcom/hoc081098/solivagant/lifecycle/Lifecycle$Event;
@@ -70,6 +66,10 @@ public final class com/hoc081098/solivagant/lifecycle/LifecycleDestroyedExceptio
public fun <init> ()V
}

public final class com/hoc081098/solivagant/lifecycle/LifecycleKt {
public static final fun getEventFlow (Lcom/hoc081098/solivagant/lifecycle/Lifecycle;)Lkotlinx/coroutines/flow/Flow;
}

public abstract interface class com/hoc081098/solivagant/lifecycle/LifecycleOwner {
public abstract fun getLifecycle ()Lcom/hoc081098/solivagant/lifecycle/Lifecycle;
}
10 changes: 5 additions & 5 deletions lifecycle/api/jvm/lifecycle.api
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ public final class com/hoc081098/solivagant/lifecycle/FlowWithLifecycleKt {
}

public abstract interface class com/hoc081098/solivagant/lifecycle/Lifecycle {
public static final field Companion Lcom/hoc081098/solivagant/lifecycle/Lifecycle$Companion;
public abstract fun getCurrentState ()Lcom/hoc081098/solivagant/lifecycle/Lifecycle$State;
public abstract fun getCurrentStateFlow ()Lkotlinx/coroutines/flow/StateFlow;
public abstract fun subscribe (Lcom/hoc081098/solivagant/lifecycle/Lifecycle$Observer;)Lcom/hoc081098/solivagant/lifecycle/Lifecycle$Cancellable;
}
@@ -13,10 +13,6 @@ public abstract interface class com/hoc081098/solivagant/lifecycle/Lifecycle$Can
public abstract fun cancel ()V
}

public final class com/hoc081098/solivagant/lifecycle/Lifecycle$Companion {
public final fun getCurrentState (Lcom/hoc081098/solivagant/lifecycle/Lifecycle;)Lcom/hoc081098/solivagant/lifecycle/Lifecycle$State;
}

public final class com/hoc081098/solivagant/lifecycle/Lifecycle$Event : java/lang/Enum {
public static final field Companion Lcom/hoc081098/solivagant/lifecycle/Lifecycle$Event$Companion;
public static final field ON_CREATE Lcom/hoc081098/solivagant/lifecycle/Lifecycle$Event;
@@ -63,6 +59,10 @@ public final class com/hoc081098/solivagant/lifecycle/LifecycleDestroyedExceptio
public fun <init> ()V
}

public final class com/hoc081098/solivagant/lifecycle/LifecycleKt {
public static final fun getEventFlow (Lcom/hoc081098/solivagant/lifecycle/Lifecycle;)Lkotlinx/coroutines/flow/Flow;
}

public abstract interface class com/hoc081098/solivagant/lifecycle/LifecycleOwner {
public abstract fun getLifecycle ()Lcom/hoc081098/solivagant/lifecycle/Lifecycle;
}
Original file line number Diff line number Diff line change
@@ -43,6 +43,9 @@ private class SolivagantLifecycleInterop(
delegate.currentStateFlow.mapState { it.asSolivagantState() }
}

override val currentState: State
get() = delegate.currentState.asSolivagantState()

override fun subscribe(observer: Lifecycle.Observer): Cancellable {
// @MainThread
var removedObserver = false
Original file line number Diff line number Diff line change
@@ -2,26 +2,18 @@ package com.hoc081098.solivagant.lifecycle.internal

import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map

/**
* Map a [StateFlow] to another [StateFlow] with the given [transform] function.
*/
internal fun <T, R> StateFlow<T>.mapState(transform: (T) -> R): StateFlow<R> =
object : StateFlow<R> {
override val replayCache: List<R>
get() = this@mapState.replayCache.map(transform)
get() = listOf(value)

override val value: R
get() = transform(this@mapState.value)

override suspend fun collect(collector: FlowCollector<R>): Nothing {
this@mapState
.map { transform(it) }
.distinctUntilChanged()
.collect(collector)

error("StateFlow collection never ends.")
}
override suspend fun collect(collector: FlowCollector<R>): Nothing =
this@mapState.collect { collector.emit(transform(it)) }
}
Original file line number Diff line number Diff line change
@@ -32,27 +32,44 @@

package com.hoc081098.solivagant.lifecycle

import com.hoc081098.solivagant.lifecycle.Lifecycle.Event
import kotlin.jvm.JvmStatic
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOn

/**
* A holder of [Lifecycle.State] that can be observed for changes.
*
* Possible transitions:
*
* ```
* [INITIALIZED] ──┐
* [INITIALIZED] ──┐(1)
* ↓
* ┌── [CREATED] ──┐
* ↓ ↑ ↓
* [DESTROYED] └── [STARTED] ──┐
* ↑ ↓
* └── [RESUMED]
* (6)┌── [CREATED] ────┐(2)
* ↓ ↑ (5) ↓
* [DESTROYED] └──── [STARTED] ──┐(3)
* ↑ ↓
* (4)└── [RESUMED]
*
* (1): ON_CREATE
* (2): ON_START
* (3): ON_RESUME
* (4): ON_PAUSE
* (5): ON_STOP
* (6): ON_DESTROY
* ```
*/
public interface Lifecycle {
public val currentStateFlow: StateFlow<State>

public val currentState: State

public fun subscribe(observer: Observer): Cancellable

public fun interface Observer {
@@ -182,9 +199,18 @@ public interface Lifecycle {
}
}
}
}

public companion object {
public val Lifecycle.currentState: State
get() = currentStateFlow.value
/**
* Creates a [Flow] of [Event]s containing values dispatched by this [Lifecycle].
*/
public val Lifecycle.eventFlow: Flow<Event>
get() = callbackFlow {
val cancellable = Lifecycle.Observer { event ->
trySend(event)
}.let { subscribe(it) }

awaitClose(cancellable::cancel)
}
}
.buffer(Channel.UNLIMITED)
.flowOn(Dispatchers.Main.immediate)
Original file line number Diff line number Diff line change
@@ -44,15 +44,43 @@ public fun LifecycleRegistry(
initialState: State,
): LifecycleRegistry = LifecycleRegistryImpl(initialState)

/**
* Possible transitions:
*
* ```
* [INITIALIZED] ──┐(1)
* ↓
* (6)┌── [CREATED] ────┐(2)
* ↓ ↑ (5) ↓
* [DESTROYED] └──── [STARTED] ──┐(3)
* ↑ ↓
* (4)└── [RESUMED]
*
* (1): ON_CREATE
* (2): ON_START
* (3): ON_RESUME
* (4): ON_PAUSE
* (5): ON_STOP
* (6): ON_DESTROY
* ```
*/
private class LifecycleRegistryImpl(initialState: State) : LifecycleRegistry {
private val _currentStateFlow = MutableStateFlow(initialState)
private var _state: State by _currentStateFlow::value

private var _state: State = _currentStateFlow.value
set(value) {
field = value
_currentStateFlow.value = value
}

private var observers: List<Observer> = emptyList()

override val currentStateFlow: StateFlow<State>
get() = _currentStateFlow.asStateFlow()

override val currentState: State
get() = _state

override fun onStateChanged(event: Event) {
when (event) {
Event.ON_CREATE -> onCreate()
@@ -121,4 +149,6 @@ private class LifecycleRegistryImpl(initialState: State) : LifecycleRegistry {
private fun checkState(required: State) {
check(_state == required) { "Expected state $required but was $_state" }
}

override fun toString(): String = "LifecycleRegistryImpl(_state=$_state)"
}
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ public object LocalLifecycleOwner {
/**
* The CompositionLocal containing the current [LifecycleOwner].
*/
@Suppress("CompositionLocalAllowlist", "MemberNameEqualsClassName")
@Suppress("MemberNameEqualsClassName")
private val LocalLifecycleOwner = staticCompositionLocalOf<LifecycleOwner> {
error("No LifecycleOwner was provided")
}
Original file line number Diff line number Diff line change
@@ -322,6 +322,7 @@ public fun LifecycleStartEffect(
private fun LifecycleStartEffectImpl(
lifecycleOwner: LifecycleOwner,
scope: LifecycleStartStopEffectScope,
@Suppress("LambdaParameterInRestartableEffect") // TODO: check this again!!!
effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult,
) {
DisposableEffect(lifecycleOwner, scope) {
@@ -625,6 +626,7 @@ public fun LifecycleResumeEffect(
private fun LifecycleResumeEffectImpl(
lifecycleOwner: LifecycleOwner,
scope: LifecycleResumePauseEffectScope,
@Suppress("LambdaParameterInRestartableEffect") // TODO: check this again!!!
effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult,
) {
DisposableEffect(lifecycleOwner, scope) {
Original file line number Diff line number Diff line change
@@ -17,7 +17,6 @@
package com.hoc081098.solivagant.lifecycle

import com.hoc081098.solivagant.lifecycle.Lifecycle.Cancellable
import com.hoc081098.solivagant.lifecycle.Lifecycle.Companion.currentState
import kotlin.coroutines.resume
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Original file line number Diff line number Diff line change
@@ -16,7 +16,6 @@

package com.hoc081098.solivagant.lifecycle

import com.hoc081098.solivagant.lifecycle.Lifecycle.Companion.currentState
import com.hoc081098.solivagant.lifecycle.internal.AtomicReference
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.coroutineContext
Original file line number Diff line number Diff line change
@@ -30,7 +30,6 @@ public fun <T : Route> SavedStateHandle.requireRoute(): T {
}
}

@Suppress("CompositionLocalAllowlist")
public val LocalNavigator: ProvidableCompositionLocal<Navigator> = staticCompositionLocalOf<Navigator> {
error("Can't use Navigator outside of a navigator NavHost")
}
Loading
Loading