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 42297c6
Show file tree
Hide file tree
Showing 131 changed files with 438 additions and 267 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.bumble.appyx.core.AppyxTestScenario
import com.bumble.appyx.core.children.nodeOrNull
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.navigation.EmptyNavModel
import com.bumble.appyx.core.navigation.EmptyState
import kotlinx.parcelize.Parcelize
import org.junit.Assert.assertEquals
import org.junit.Rule
Expand Down Expand Up @@ -50,7 +51,7 @@ class PermanentChildTest {
buildContext: BuildContext,
) : ParentNode<TestParentNode.NavTarget>(
buildContext = buildContext,
navModel = EmptyNavModel<NavTarget, Any>(),
navModel = EmptyNavModel<NavTarget, EmptyState>(),
) {

@Parcelize
Expand Down
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,13 +1,14 @@
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, State: Parcelable> : BaseNavModel<NavTarget, State>(
savedStateMap = null,
finalState = null,
screenResolver = OnScreenStateResolver { true }
Expand Down
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
Loading

0 comments on commit 42297c6

Please sign in to comment.