Skip to content

Commit

Permalink
[#310] Ensure NavTarget and State are parcelable
Browse files Browse the repository at this point in the history
  • Loading branch information
LachlanMcKee committed Jan 2, 2023
1 parent 4f6678e commit e2a15d0
Show file tree
Hide file tree
Showing 126 changed files with 418 additions and 261 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.bumble.appyx.core.children

import android.os.Parcelable
import com.bumble.appyx.core.navigation.NavKey
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.state.SavedStateMap

// custom equals/hashCode for MutableStateFlow and equality checks
sealed class ChildEntry<T> {
sealed class ChildEntry<T : Parcelable> {
abstract val key: NavKey<T>

override fun equals(other: Any?): Boolean =
Expand All @@ -18,12 +19,12 @@ sealed class ChildEntry<T> {
"$key@${javaClass.simpleName}"

/** All public APIs should return this type of child which is ready to work with. */
class Initialized<T>(
class Initialized<T : Parcelable>(
override val key: NavKey<T>,
val node: Node,
) : ChildEntry<T>()

class Suspended<T>(
class Suspended<T : Parcelable>(
override val key: NavKey<T>,
val savedState: SavedStateMap?,
) : ChildEntry<T>()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.bumble.appyx.core.children

import android.os.Parcelable
import com.bumble.appyx.core.node.Node

val <T> ChildEntry<T>.nodeOrNull: Node?
val <T : Parcelable> ChildEntry<T>.nodeOrNull: Node?
get() =
when (this) {
is ChildEntry.Initialized -> node
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bumble.appyx.core.children

import android.os.Parcelable
import androidx.lifecycle.coroutineScope
import com.bumble.appyx.core.modality.AncestryInfo
import com.bumble.appyx.core.modality.BuildContext
Expand All @@ -21,7 +22,7 @@ import kotlinx.coroutines.launch
*
* Lifecycle of these nodes is managed in [com.bumble.appyx.core.lifecycle.ChildNodeLifecycleManager].
*/
internal class ChildNodeCreationManager<NavTarget : Any>(
internal class ChildNodeCreationManager<NavTarget : Parcelable>(
private var savedStateMap: SavedStateMap?,
private val customisations: NodeCustomisationDirectory,
private val keepMode: ChildEntry.KeepMode,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bumble.appyx.core.composable

import android.os.Parcelable
import androidx.compose.animation.core.Transition
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
Expand Down Expand Up @@ -32,7 +33,7 @@ import com.bumble.appyx.core.navigation.transition.TransitionParams
import kotlinx.coroutines.flow.map

@Composable
fun <NavTarget : Any, State> ParentNode<NavTarget>.Child(
fun <NavTarget : Parcelable, State : Parcelable> ParentNode<NavTarget>.Child(
navElement: NavElement<NavTarget, out State>,
saveableStateHolder: SaveableStateHolder,
transitionParams: TransitionParams,
Expand Down Expand Up @@ -87,7 +88,7 @@ private class ChildRendererImpl(
}

@Composable
fun <NavTarget : Any, State> ParentNode<NavTarget>.Child(
fun <NavTarget : Parcelable, State : Parcelable> ParentNode<NavTarget>.Child(
navElement: NavElement<NavTarget, out State>,
modifier: Modifier = Modifier,
transitionHandler: TransitionHandler<NavTarget, State> = JumpToEndTransitionHandler(),
Expand Down Expand Up @@ -125,7 +126,7 @@ fun <NavTarget : Any, State> ParentNode<NavTarget>.Child(
}
}

private fun <NavTarget : Any, State> NavElement<NavTarget, State>.createDescriptor(
private fun <NavTarget : Parcelable, State : Parcelable> NavElement<NavTarget, State>.createDescriptor(
transitionParams: TransitionParams
) =
TransitionDescriptor(
Expand All @@ -137,15 +138,15 @@ private fun <NavTarget : Any, State> NavElement<NavTarget, State>.createDescript
)

@Composable
fun <R, S> NavModel<R, S>?.childrenAsState(): State<NavElements<R, out S>> =
fun <R : Parcelable, S : Parcelable> NavModel<R, S>?.childrenAsState(): State<NavElements<R, out S>> =
if (this != null) {
elements.collectAsState()
} else {
remember { mutableStateOf(emptyList()) }
}

@Composable
fun <R, S> NavModel<R, S>?.visibleChildrenAsState(): State<NavElements<R, out S>> =
fun <R : Parcelable, S : Parcelable> NavModel<R, S>?.visibleChildrenAsState(): State<NavElements<R, out S>> =
if (this != null) {
val visibleElementsFlow = remember { screenState.map { it.onScreen } }
visibleElementsFlow.collectAsState(emptyList())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bumble.appyx.core.composable

import android.os.Parcelable
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
Expand All @@ -26,7 +27,7 @@ import kotlinx.coroutines.flow.map
import kotlin.reflect.KClass

@Composable
inline fun <reified NavTarget : Any, State> ParentNode<NavTarget>.Children(
inline fun <reified NavTarget : Parcelable, State : Parcelable> ParentNode<NavTarget>.Children(
navModel: NavModel<NavTarget, State>,
modifier: Modifier = Modifier,
transitionHandler: TransitionHandler<NavTarget, State> = JumpToEndTransitionHandler(),
Expand Down Expand Up @@ -63,7 +64,7 @@ inline fun <reified NavTarget : Any, State> ParentNode<NavTarget>.Children(
}
}

class ChildrenTransitionScope<T : Any, S>(
class ChildrenTransitionScope<T : Parcelable, S : Parcelable>(
private val transitionHandler: TransitionHandler<T, S>,
private val transitionParams: TransitionParams,
private val navModel: NavModel<T, S>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bumble.appyx.core.lifecycle

import android.os.Parcelable
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleRegistry
import com.bumble.appyx.core.children.ChildEntry
Expand All @@ -23,7 +24,7 @@ import kotlinx.coroutines.launch
* Hosts [LifecycleRegistry] to manage the current node lifecycle
* and updates lifecycle of children nodes when updated.
*/
internal class ChildNodeLifecycleManager<NavTarget>(
internal class ChildNodeLifecycleManager<NavTarget : Parcelable>(
private val navModel: NavModel<NavTarget, *>,
private val children: StateFlow<ChildEntryMap<NavTarget>>,
private val coroutineScope: CoroutineScope,
Expand Down Expand Up @@ -86,7 +87,7 @@ internal class ChildNodeLifecycleManager<NavTarget>(
}
}

private fun <NavTarget> Flow<NavModelAdapter.ScreenState<NavTarget, *>>.keys() =
private fun <NavTarget : Parcelable> Flow<NavModelAdapter.ScreenState<NavTarget, *>>.keys() =
this
.map { screenState ->
ScreenState(
Expand All @@ -100,7 +101,7 @@ internal class ChildNodeLifecycleManager<NavTarget>(
nodeOrNull?.updateLifecycleState(state)
}

private data class ScreenState<NavTarget>(
private data class ScreenState<NavTarget : Parcelable>(
val onScreen: List<NavKey<NavTarget>> = emptyList(),
val offScreen: List<NavKey<NavTarget>> = emptyList(),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bumble.appyx.core.navigation

import android.os.Parcelable
import androidx.activity.OnBackPressedCallback
import com.bumble.appyx.core.mapState
import com.bumble.appyx.core.navigation.backpresshandlerstrategies.BackPressHandlerStrategy
Expand Down Expand Up @@ -28,7 +29,7 @@ import kotlin.coroutines.EmptyCoroutineContext
*
* If more granular configuration is required, you should inherit NavModel interface instead.
*/
abstract class BaseNavModel<NavTarget, State>(
abstract class BaseNavModel<NavTarget : Parcelable, State : Parcelable>(
private val backPressHandler: BackPressHandlerStrategy<NavTarget, State> = DontHandleBackPress(),
private val operationStrategy: OperationStrategy<NavTarget, State> = ExecuteImmediately(),
private val screenResolver: OnScreenStateResolver<State>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package com.bumble.appyx.core.navigation

import android.os.Parcelable
import com.bumble.appyx.core.navigation.onscreen.OnScreenStateResolver

/**
* An implementation of a NavModel that won't add any children.
* This is potentially useful if your ParentNode only uses
* [com.bumble.appyx.core.navigation.model.permanent.PermanentNavModel]
*/
class EmptyNavModel<NavTarget, State> : BaseNavModel<NavTarget, State>(
class EmptyNavModel<NavTarget : Parcelable> : BaseNavModel<NavTarget, EmptyState>(
savedStateMap = null,
finalState = null,
screenResolver = OnScreenStateResolver { true }
) {
override val initialElements: NavElements<NavTarget, State>
override val initialElements: NavElements<NavTarget, EmptyState>
get() = emptyList()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.bumble.appyx.core.navigation

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Suppress("PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY")
@Parcelize
class EmptyState internal constructor() : Parcelable {
companion object {
val INSTANCE = EmptyState()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import kotlinx.parcelize.RawValue

@Parcelize
@Immutable
class NavElement<NavTarget, State> private constructor(
val key: @RawValue NavKey<NavTarget>,
val fromState: @RawValue State,
val targetState: @RawValue State,
val operation: @RawValue Operation<NavTarget, State>,
class NavElement<NavTarget : Parcelable, State : Parcelable> private constructor(
val key: NavKey<NavTarget>,
val fromState: State,
val targetState: State,
val operation: Operation<NavTarget, State>,
val transitionHistory: List<Pair<State, State>>
) : Parcelable {
constructor(
key: @RawValue NavKey<NavTarget>,
fromState: @RawValue State,
targetState: @RawValue State,
operation: @RawValue Operation<NavTarget, State>,
key: NavKey<NavTarget>,
fromState: State,
targetState: State,
operation: Operation<NavTarget, State>,
) : this(
key,
fromState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import java.util.UUID

@Parcelize
@Immutable
class NavKey<NavTarget> private constructor(
val navTarget: @RawValue NavTarget,
class NavKey<NavTarget : Parcelable> private constructor(
val navTarget: NavTarget,
val id: String
) : Parcelable {

constructor(navTarget: @RawValue NavTarget) : this(
constructor(navTarget: NavTarget) : this(
navTarget = navTarget,
id = UUID.randomUUID().toString()
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.bumble.appyx.core.navigation

import android.os.Parcelable
import androidx.compose.runtime.Stable
import com.bumble.appyx.core.plugin.BackPressHandler
import com.bumble.appyx.core.plugin.SavesInstanceState
import com.bumble.appyx.core.plugin.UpNavigationHandler
import kotlinx.coroutines.flow.StateFlow

@Stable
interface NavModel<NavTarget, State> : NavModelAdapter<NavTarget, State>,
interface NavModel<NavTarget : Parcelable, State : Parcelable> : NavModelAdapter<NavTarget, State>,
UpNavigationHandler,
SavesInstanceState,
BackPressHandler {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package com.bumble.appyx.core.navigation

import android.os.Parcelable
import androidx.compose.runtime.Stable
import kotlinx.coroutines.flow.StateFlow

@Stable
interface NavModelAdapter<NavTarget, State> {
interface NavModelAdapter<NavTarget : Parcelable, State : Parcelable> {

val screenState: StateFlow<ScreenState<NavTarget, out State>>

data class ScreenState<NavTarget, State>(
data class ScreenState<NavTarget : Parcelable, State : Parcelable>(
val onScreen: NavElements<NavTarget, out State> = emptyList(),
val offScreen: NavElements<NavTarget, out State> = emptyList(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.bumble.appyx.core.navigation
import android.os.Parcelable
import kotlinx.parcelize.Parcelize

interface Operation<NavTarget, State> :
interface Operation<NavTarget : Parcelable, State : Parcelable> :
(NavElements<NavTarget, State>) -> NavElements<NavTarget, State>, Parcelable {

fun isApplicable(elements: NavElements<NavTarget, State>): Boolean
Expand Down Expand Up @@ -67,7 +67,7 @@ interface Operation<NavTarget, State> :
}

@Parcelize
class Noop<NavTarget, State> : Operation<NavTarget, State> {
class Noop<NavTarget : Parcelable, State : Parcelable> : Operation<NavTarget, State> {

override fun isApplicable(elements: NavElements<NavTarget, State>) = false

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.bumble.appyx.core.navigation.backpresshandlerstrategies

import android.os.Parcelable
import com.bumble.appyx.core.navigation.BaseNavModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow

interface BackPressHandlerStrategy<NavTarget, State> {
interface BackPressHandlerStrategy<NavTarget : Parcelable, State : Parcelable> {

fun init(navModel: BaseNavModel<NavTarget, State>, scope: CoroutineScope)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.bumble.appyx.core.navigation.backpresshandlerstrategies

import android.os.Parcelable
import com.bumble.appyx.core.navigation.BaseNavModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn

abstract class BaseBackPressHandlerStrategy<NavTarget, State>
abstract class BaseBackPressHandlerStrategy<NavTarget : Parcelable, State : Parcelable>
: BackPressHandlerStrategy<NavTarget, State> {

protected lateinit var scope: CoroutineScope
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.bumble.appyx.core.navigation.backpresshandlerstrategies

import android.os.Parcelable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf

class DontHandleBackPress<NavTarget, State> :
class DontHandleBackPress<NavTarget : Parcelable, State : Parcelable> :
BaseBackPressHandlerStrategy<NavTarget, State>() {

override val canHandleBackPressFlow: Flow<Boolean> =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bumble.appyx.core.navigation.model.combined

import android.os.Parcelable
import androidx.activity.OnBackPressedCallback
import com.bumble.appyx.core.combineState
import com.bumble.appyx.core.navigation.NavElements
Expand All @@ -14,9 +15,9 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.StateFlow
import kotlin.coroutines.EmptyCoroutineContext

class CombinedNavModel<NavTarget>(
class CombinedNavModel<NavTarget : Parcelable>(
val navModels: List<NavModel<NavTarget, *>>,
) : NavModel<NavTarget, Any?>, Destroyable {
) : NavModel<NavTarget, Parcelable>, Destroyable {

constructor(vararg navModels: NavModel<NavTarget, *>) : this(navModels.toList())

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.bumble.appyx.core.navigation.model.combined

import android.os.Parcelable
import com.bumble.appyx.core.navigation.NavModel

operator fun <NavTarget> NavModel<NavTarget, *>.plus(
operator fun <NavTarget : Parcelable> NavModel<NavTarget, *>.plus(
other: NavModel<NavTarget, *>,
): CombinedNavModel<NavTarget> {
val currentModels = if (this is CombinedNavModel<NavTarget>) navModels else listOf(this)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bumble.appyx.core.navigation.model.permanent

import com.bumble.appyx.core.navigation.EmptyState
import com.bumble.appyx.core.navigation.NavElement

typealias PermanentElement<T> = NavElement<T, Int>
typealias PermanentElement<T> = NavElement<T, EmptyState>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bumble.appyx.core.navigation.model.permanent

import com.bumble.appyx.core.navigation.EmptyState
import com.bumble.appyx.core.navigation.NavElements

typealias PermanentElements<T> = NavElements<T, Int>
typealias PermanentElements<T> = NavElements<T, EmptyState>
Loading

0 comments on commit e2a15d0

Please sign in to comment.