Skip to content

Commit

Permalink
Updated documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
chRyNaN committed Aug 20, 2022
1 parent 89f01a8 commit 1333f5a
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 131 deletions.
160 changes: 56 additions & 104 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ Kotlin multi-platform application navigation library. Supports Jetpack Compose.
<img alt="GitHub tag (latest by date)" src="https://img.shields.io/github/v/tag/chRyNaN/navigation">

```kotlin
val navigator = rememberNavigatorByKey("Greeting") { key ->
when (key) {
val navigator = rememberNavigator("Greeting")

NavContainer(navigator) { _, destination ->
when (destination) {
"Greeting" -> Text("Hello")
"Farewell" -> Text("Good-bye")
else -> Text("Unexpected Key: $key")
Expand All @@ -17,139 +19,89 @@ navigator.goTo("Farewell")

### Usage

Navigation is handled differently for each platform and UI framework. This library provides some common navigation
components that serve as a recommended structure, but each platform and UI framework is independently responsible for
handling navigation and choosing whether to conform to the provided components.

#### Jetpack Compose

There are three different `ComposeNavigators` that can handle navigation in Jetpack Compose. Which one to use depends on
the application needs and personal preference.

##### Navigate by content

This approach allows for specifying the `@Composable` content on demand, rather than up-front.
* Create a `Navigator` for any `NavigationDestination` type that you will use as a destination key. This can be Strings,
enums, sealed classes, or any type.

```kotlin
val navigator = rememberNavigatorByContent("Greeting") { Text("Hello") }

// The NavContainer will start by displaying the initial content, which in this case is "Hello".
NavContainer(navigator)

// The above NavContainer will display "Good-bye" after the following call:
navigator.goTo("Farewell") { Text("Good-bye") }

// Goes back to the initial content: "Hello":
navigator.goBack()
```

##### Navigate by key
enum class Destination {

This approach allows for specifying the `@Composable` content for each key up-front. Then navigation can be done by
simply providing a key.

```kotlin
val navigator = rememberNavigatorByKey("Greeting") { key ->
when (key) {
"Greeting" -> Text("Hello")
"Farewell" -> Text("Good-bye")
else -> Text("Unexpected Key: $key")
}
HOME,
SETTINGS,
DETAILS
}

// The NavContainer will start by displaying the initial content, which in this case is "Hello"
NavContainer(navigator)

// The above NavContainer will display "Good Bye" after the following call:
navigator.goTo("Farewell")

// Goes back to the initial content: "Hello":
navigator.goBack()
@Composable
fun App() {
val navigator = rememberNavigator(initialDestination = Destination.HOME)
}
```

##### Navigate by NavigationIntent

This approach is similar to the key approach, but the key is a `NavigationIntent` and the returned `ComposeNavigator`
implements the `NavigationEventNavigator` interface from the `core` module.
* Create a `NavContainer` to display the current content based off of the latest navigation context and destination
values.

```kotlin
val navigator = rememberNavigatorByIntent(HomeNavigationIntent.Greeting) { navigationIntent ->
when (navigationIntent) {
HomeNavigationIntent.Greeting -> Text("Hello")
HomeNavigationIntent.Farewell -> Text("Good-bye")
@Composable
fun App() {
val navigator = rememberNavigator(initialDestination = Destination.HOME)

NavContainer(navigator = navigator) { _, destination ->
when (destination) {
Destination.HOME -> HomeScreenComposable()
Destination.SETTINGS -> SettingsScreenComposable()
Destination.DETAILS -> DetailsScreenComposable()
}
}
}

// The NavContainer will start by displaying the initial content, which in this case is "Hello"
NavContainer(navigator)

// The above NavContainer will display "Good Bye" after the following call:
navigator.goTo(HomeNavigationIntent.Farewell)

// Goes back to the initial content: "Hello":
navigator.goBack()
```

##### Nested navigation

Within the scope of the content blocks, you can access the `navigator` property which can be used for nested navigation:
* Use the `Navigator` instance to navigate between navigation contexts and destinations.

```kotlin
val navigator = rememberNavigatorByKey("Hello") { key ->
when (key) {
"Greeting" -> Button("Hello") {
navigator.goBack() // Safe access to the navigator property within this scope.
}
"Farewell" -> Text("Good-bye")
else -> Text("Unexpected Key: $key")
}
}
BackHandler { navigator.goBack() }
```

##### Key State changes
#### Navigation Contexts

The key of the currently displayed `@Composable` can be accessed by the `ComposeNavigator.currentKey` property:
Complex navigation flows require multiple stacks of navigation destinations that may be retained when changing stacks.
These stacks are referred to as `NavigationContext` in this library. A `NavigationContext` is an interface that defines
the default navigation destination. For example:

```kotlin
navigator.currentKey
```
enum class MainNavigationContext(
val title: String,
val icon: ImageVector,
override val initialDestination: Destination
) : NavigationContext<Destination> {

To listen to changes to the current key, use the `ComposeNavigator.keyChanges` property along with
the `Flow<T>.collectAsState` function:
HOME(title = "Home", icon = Icons.Default.Home, initialDestination = Destination.HOME),

```kotlin
val currentKey by navigator.keyChanges.collectAsState(initial = currentKey)
SETTINGS(title = "Settings", icon = Icons.Default.Settings, initialDestination = Destination.SETTINGS)
}
```

For convenience, there is an extension function that performs the above logic: `ComposeNavigator.currentKeyAsState()`
This is especially useful when a `@Composable` needs to be updated when the key changes, for instance in a Bottom
Navigation component:
Then to change the current context, use the `Navigator.changeContext` function:

```kotlin
val currentKey by navigator.currentKeyAsState()

BottomNavigation {
listOf(ScreenIntent.ColorList, ScreenIntent.Palette).forEach {
BottomNavigationItem(
selected = currentKey == it,
onClick = {
navigator.goTo(it)
}
)
}
}
navigator.changeContext(MainNavigationContext.SETTINGS)
```

#### Android
#### Transitions and animations

To create a `Navigator` use one the provided `navigator()` functions. For instance:
You have complete control over the composables that render the UI of the application and can use the Jetpack Compose
library's transition and animation APIs to change between the navigation context and destination UIs. For more
fine-grained control, create a custom composable replacing the `NavContainer` that handles transitions properly for your
application. Then just listen and react to the `Navigator.state` changes.

```kotlin
val navigator = navigator<NavigationIntent>(activity = activity, onGoTo = { navigationIntent ->
activity.startActivity(...)
})

navigator.goBack()
@Composable
fun <Destination : NavigationDestination, Context : NavigationContext<Destination>> MyNavContainer(
navigator: Navigator<Destination, Context>,
) {
val context = navigator.state.currentContextAsState()
val destination = navigator.state.currentDestinationAsState()

// Render UI from context and destination values and apply any transition or animtation desired.
}
```

## Building the library
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ import com.chrynan.navigation.NavigationContext
import com.chrynan.navigation.NavigationDestination
import com.chrynan.navigation.Navigator

/**
* A [Composable] that listens to navigation context and destination changes from the provided [navigator] and calls
* the provided [content] [Composable] function with the latest values.
*
* Example usage:
* ```kotlin
* NavContainer(navigator) { context, destination ->
* Text("context = $context; destination = $destination")
* }
* ```
*/
@Composable
@ExperimentalNavigationApi
fun <Destination : NavigationDestination, Context : NavigationContext<Destination>> NavContainer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,30 @@ import androidx.compose.runtime.remember
import com.chrynan.navigation.*

/**
* Creates and remembers a [ComposeNavigationDestinationState] that can navigate with a key. This allows for specifying the [Composable]
* content up front when creating this [ComposeNavigatorByKey] and simply navigating with a key from the
* [ComposeNavigatorByKey.goTo] function.
* Creates and remembers a [Navigator]. A [Navigator] can be used to navigate between different UI content in an
* application.
*
* Example usage:
* ```kotlin
* val navigator = rememberNavigatorByKey("Greeting") { key ->
* when(key) {
* val navigator = rememberNavigator(initialDestination = "Greeting")
*
* // The NavContainer will start by displaying the initial content, which in this case is "Hello"
* NavContainer(navigator) { _, key ->
* when (key) {
* "Greeting" -> Text("Hello")
* "Farewell" -> Text("Good-bye")
* else -> Text("Unexpected Key: $key")
* }
* }
*
* // The NavContainer will start by displaying the initial content, which in this case is "Hello"
* NavContainer(navigator)
*
* // The above NavContainer will display "Good Bye" after the following call:
* navigator.goTo("Farewell")
*
* // Goes back to the initial content: "Hello":
* navigator.goBack()
* ```
*
* **Note:** That it is typical to use a [ComposeNavigationDestinationState] with a [NavContainer] to display the [Composable] content
* and listen to changes.
* **Note:** Use the [NavContainer] to display the [Composable] content for the current navigation context and keys.
*
* **Note:** This function differs slightly from the [rememberNavigator] function in that it only uses a single
* scope of type [Nothing]. This means that scopes cannot be changed on the returned [ComposeNavigatorImpl].
Expand All @@ -48,25 +46,21 @@ fun <Destination : NavigationDestination> rememberNavigator(
}

/**
* Creates and remembers a [ComposeNavigationDestinationState] that can navigate with a key. This allows for specifying the [Composable]
* content up front when creating this [ComposeNavigatorByKey] and simply navigating with a key from the
* [ComposeNavigatorByKey.goTo] function.
* Creates and remembers a [Navigator]. A [Navigator] can be used to navigate between different UI content in an
* application.
*
* Example usage:
* ```kotlin
* val navigator = rememberNavigatorByKey(
* initialContext = BottomNavBarItem.HELLO,
* content = { key ->
* when(key) {
* val navigator = rememberNavigator(initialContext = BottomNavBarItem.HELLO)
*
* // The NavContainer will start by displaying the initial content, which in this case is "Hello"
* NavContainer(navigator) { context, key ->
* when (key) {
* "Greeting" -> Text("Hello")
* "Farewell" -> Text("Good-bye")
* else -> Text("Unexpected Key: $key")
* }
* }
* )
*
* // The NavContainer will start by displaying the initial content, which in this case is "Hello"
* NavContainer(navigator)
*
* // The above NavContainer will display "Good Bye" after the following call:
* navigator.goTo("Farewell")
Expand All @@ -78,8 +72,7 @@ fun <Destination : NavigationDestination> rememberNavigator(
* navigator.changeContext(BottomNavBarItem.GOODBYE)
* ```
*
* **Note:** That it is typical to use a [ComposeNavigationDestinationState] with a [NavContainer] to display the [Composable] content
* and listen to changes.
* **Note:** Use the [NavContainer] to display the [Composable] content for the current navigation context and keys.
*
* **Note:** That this function differs slightly from the [rememberNavigator] function in that this function allows
* changing of scopes, which is useful for more complex navigation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ import com.chrynan.navigation.compose.rememberNavigator
@ExperimentalNavigationApi
@Composable
fun App() {
val navigator = rememberNavigator(
initialContext = MainNavigationContext.HOME
)
val navigator = rememberNavigator(initialContext = MainNavigationContext.HOME)

Column {
Box(modifier = Modifier.weight(1f)) {
NavContainer(navigator = navigator) { context, destination ->

when (destination) {
Destination.HOME -> Text("Home Screen")
Destination.DETAILS -> Text("Details Screen")
Destination.SETTINGS -> Text("Settings Screen")
}
}
}

Expand Down

0 comments on commit 1333f5a

Please sign in to comment.