Skip to content

Commit

Permalink
[bumble-tech#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 9806dbe
Show file tree
Hide file tree
Showing 150 changed files with 581 additions and 335 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Pending changes

- [#311](https://github.com/bumble-tech/appyx/pull/311)**Breaking change**: `NavTarget` and `State` are now explicitly `Parcelable`. Please see this PR to see approaches to resolve any compilation issues you may notice.
- [#287](https://github.com/bumble-tech/appyx/pull/287)**Added**: Introduced a new `rememberCombinedHandler` implementation that takes an immutable list to avoid non-skippable compositions. The previous implementation is now deprecated.
- [#287](https://github.com/bumble-tech/appyx/pull/287)**Added**: `ImmutableList` has been added to avoid non-skippable compositions.
- [#289](https://github.com/bumble-tech/appyx/issues/289)**Added**: Introduced `interop-rx3` for RxJava 3 support. This has identical functionality to `interop-rx2`.
Expand Down
9 changes: 5 additions & 4 deletions documentation/navmodel/backstack.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ The back stack also supports different back press and operation strategies (see
## States

```kotlin
enum class State {
@Parcelize
enum class State : Parcelable {
CREATED, ACTIVE, STASHED, DESTROYED,
}
```
Expand All @@ -33,7 +34,7 @@ Check out the apps in our [Coding challenges](../how-to-use-appyx/coding-challen
As the back stack can never be empty, it's required to define an initial target.

```kotlin
class BackStack<NavTarget : Any>(
class BackStack<NavTarget : Parcelable>(
initialElement: NavTarget,
savedStateMap: SavedStateMap?,
// Optional parameters are omitted
Expand Down Expand Up @@ -131,7 +132,7 @@ Effect on stack: depends on the contents of the stack:
You can override the default strategy in the constructor. You're not limited to using the provided classes, feel free to implement your own.

```kotlin
class BackStack<NavTarget : Any>(
class BackStack<NavTarget : Parcelable>(
/* ... */
backPressHandler: BackPressHandlerStrategy<NavTarget, State> = PopBackPressHandler(),
/* ... */
Expand All @@ -152,7 +153,7 @@ Serves as a no-op.
You can override the default strategy in the constructor. You're not limited to using the provided classes, feel free to implement your own.

```kotlin
class BackStack<NavTarget : Any>(
class BackStack<NavTarget : Parcelable>(
/* ... */
operationStrategy: OperationStrategy<NavTarget, State> = ExecuteImmediately(),
/* ... */
Expand Down
11 changes: 9 additions & 2 deletions documentation/navmodel/cards.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,20 @@ The `Cards` NavModel is not currently published, however you can try it in `:sam
## States

```kotlin
sealed class State {
sealed class State: Parcelable {
@Parcelize
data class Queued(val queueNumber: Int) : State()
@Parcelize
object Bottom : State()
@Parcelize
object Top : State()
@Parcelize
object IndicateLike : State()
@Parcelize
object IndicatePass : State()
@Parcelize
object VoteLike : State()
@Parcelize
object VotePass : State()
}
```
Expand All @@ -35,7 +42,7 @@ sealed class State {
Requires defining items that will be converted to profile cards. The first one in the list will become a `Top` card, the second one a `Bottom` card, the rest will be `Queued`.

```kotlin
class Cards<NavTarget : Any>(
class Cards<NavTarget : Parcelable>(
initialItems: List<NavTarget> = listOf(),
) : BaseNavModel<NavTarget, State>(
screenResolver = CardsOnScreenResolver,
Expand Down
9 changes: 5 additions & 4 deletions documentation/navmodel/custom.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ A step-by-step guide. You can also take a look at other existing examples to see
Create the class; define your possible states; define your initial state.

```kotlin
class Foo<NavTarget : Any>(
class Foo<NavTarget : Parcelable>(
initialItems: List<NavTarget> = listOf(),
savedStateMap: SavedStateMap?
) : BaseNavModel<NavTarget, Foo.State>(
Expand All @@ -17,7 +17,8 @@ class Foo<NavTarget : Any>(
) {

// Your possible states for any single navigation target
enum class State {
@Parcelize
enum class State : Parcelable {
CREATED, FOO, BAR, BAZ, DESTROYED;
}

Expand Down Expand Up @@ -54,7 +55,7 @@ Define one or more operations.

```kotlin
@Parcelize
class SomeOperation<NavTarget : Any> : FooOperation<NavTarget> {
class SomeOperation<NavTarget : Parcelable> : FooOperation<NavTarget> {

override fun isApplicable(elements: FooElements<NavTarget>): Boolean =
TODO("Define whether this operation is applicable given the current state")
Expand All @@ -74,7 +75,7 @@ class SomeOperation<NavTarget : Any> : FooOperation<NavTarget> {
}

// You can add an extension method for a leaner API
fun <NavTarget : Any> Foo<NavTarget>.someOperation() {
fun <NavTarget : Parcelable> Foo<NavTarget>.someOperation() {
accept(FooOperation())
}
```
Expand Down
3 changes: 2 additions & 1 deletion documentation/navmodel/promoter.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ If you feel that this functionality should be part of the main library, please l
## States

```kotlin
enum class State {
@Parcelize
enum class State : Parcelable {
CREATED, STAGE1, STAGE2, STAGE3, STAGE4, SELECTED, DESTROYED
}
```
Expand Down
9 changes: 5 additions & 4 deletions documentation/navmodel/spotlight.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ It's great for flows or tabbed containers.
## States

```kotlin
enum class State {
@Parcelize
enum class State : Parcelable {
INACTIVE_BEFORE, ACTIVE, INACTIVE_AFTER;
}
```
Expand All @@ -19,7 +20,7 @@ enum class State {
Requires defining items and an active index.

```kotlin
class Spotlight<NavTarget : Any>(
class Spotlight<NavTarget : Parcelable>(
items: List<NavTarget>,
initialActiveIndex: Int = 0,
savedStateMap: SavedStateMap?,
Expand Down Expand Up @@ -95,7 +96,7 @@ Replaces elements held by the spotlight instance with a new list. Transitions ne
You can override the default strategy in the constructor. You're not limited to using the provided classes, feel free to implement your own.

```kotlin
class Spotlight<NavTarget : Any>(
class Spotlight<NavTarget : Parcelable>(
/* ... */
backPressHandler: BackPressHandlerStrategy<NavTarget, State> = GoToDefault(
initialActiveIndex
Expand All @@ -118,7 +119,7 @@ Runs a `Previous` operation.
You can override the default strategy in the constructor. You're not limited to using the provided classes, feel free to implement your own.

```kotlin
class Spotlight<NavTarget : Any>(
class Spotlight<NavTarget : Parcelable>(
/* ... */
operationStrategy: OperationStrategy<NavTarget, State> = ExecuteImmediately(),
/* ... */
Expand Down
3 changes: 2 additions & 1 deletion documentation/navmodel/tiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ If you feel that this functionality should be part of the main library, please l
## States

```kotlin
enum class State {
@Parcelize
enum class State : Parcelable {
CREATED, STANDARD, SELECTED, DESTROYED
}
```
Expand Down
2 changes: 1 addition & 1 deletion documentation/ui/children-view.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ override fun View(modifier: Modifier) {
}

@Composable
private fun GridExample(elements: List<NavElement<NavTarget, out Any?>>) {
private fun GridExample(elements: List<NavElement<NavTarget, out Parcelable?>>) {
LazyVerticalGrid(
columns = Fixed(2),
modifier = Modifier.fillMaxSize(),
Expand Down
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
Loading

0 comments on commit 9806dbe

Please sign in to comment.