Skip to content

Type-safe arguments for Jetpack Navigation Compose using Kotlinx.Serialization

License

Notifications You must be signed in to change notification settings

strajky5/navigation-compose-typed

 
 

Repository files navigation

Navigation Compose Typed

Compile-time type-safe arguments for the Jetpack Navigation Compose library. Based on KotlinX.Serialization.

Kiwi.com library CI Build GitHub release Maven release

Major features:

  • Complex types' support, including nullability for primitive types - the only condition is that the type has to be serializable with KotlinX.Serializable library.
  • Based on official Kotlin Serialization compiler plugin - no slowdown with KSP or KAPT.
  • All Jetpack Navigation Compose features: e.g. navigateUp() after a deeplink preserves the top-level shared arguments.
  • Few simple functions, no new complex NavHost or NavController types; this allows covering other Jetpack Navigation Compose extensions.
  • Gradual integration, feel free to onboard just a part of your app.

QuickStart

Add this library dependency and KotlinX.Serialization support

plugins {
	id("org.jetbrains.kotlin.plugin.serialization") version "1.7.10"
}
dependencies {
	implementation("com.kiwi.navigation-compose.typed:core:<version>")
	implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")
}

Warning This library uses Semantic Versioning. Be aware that BC breaks are allowed in minor versions before the major 1.0 version.

Create app's destinations

import com.kiwi.navigationcompose.typed.Destination

sealed interface Destinations : Destination {
	@Serializable
	object Home : Destinations

	@Serializable
	data class Article(
		val id: String,
	) : Destinations
}

and use them in the navigation graph definition

import com.kiwi.navigationcompose.typed.composable
import com.kiwi.navigationcompose.typed.createRoutePattern

NavGraph(
	startDestination = createRoutePattern<Destinations.Home>(),
) {
	composable<Destinations.Home> {
		Home()
	}
	composable<Destinations.Article> {
		// this is Destinations.Article
		Article(id)
	}
}

Now, it is time to navigate! Create a Destination instance and pass it to the navigate extension method on the standard NavController.

import com.kiwi.navigationcompose.typed.Destination
import com.kiwi.navigationcompose.typed.navigate

@Composable
fun AppNavHost() {
	val navController = rememberNavController()
	NavGraph(
		navController = navController,
	) {
		composable<Destinations.Home> {
			Home(navController::navigate)
		}
	}
}

@Composable
private fun Home(
	onNavigate: (Destination) -> Unit,
) {
	Home(
		onArticleClick = { id -> onNavigate(Destinations.Article(id)) },
	)
}

@Composable
private fun Home(
	onArticleClick: (id: Int) -> Unit,
) {
	Column {
		Button(onClick = { onArticleClick(1) }) { Text("...") }
		Button(onClick = { onArticleClick(2) }) { Text("...") }
	}
}

Extensibility

What about cooperation with Accompanist's AnimatedNavHost or bottomSheet {}? Do not worry. Basically, all these are just a few simple functions. Create your own abstraction and use createRoutePattern(), createNavArguments(), decodeArguments() and registerDestinationType() functions.

import com.kiwi.navigationcompose.typed.createRoutePattern
import com.kiwi.navigationcompose.typed.createNavArguments
import com.kiwi.navigationcompose.typed.decodeArguments
import com.kiwi.navigationcompose.typed.Destination
import com.kiwi.navigationcompose.typed.registerDestinationType

private inline fun <reified T : Destination> NavGraphBuilder.bottomSheet(
	noinline content: @Composable T.(NavBackStackEntry) -> Unit,
) {
	val serializer = serializer<T>()
	registerDestinationType(T::class, serializer)
	bottomSheet(
		route = createRoutePattern(serializer),
		arguments = createNavArguments(serializer),
	) {
		val arguments = decodeArguments(serializer, it)
		arguments.content(it)
	}
}

NavGraph {
	bottomSheet<Destinations.Article> {
		Article(id)
	}
}

Result sharing

Another set of functionality is provided to support the result sharing. First, define the destination as ResultDestination type and specify the result type class. Then open the screen as usual and utilize ComposableResultEffect or DialogResultEffect to observe the destination's result. To send the result, use NavController's extension setResult.

import com.kiwi.navigationcompose.typed.Destination
import com.kiwi.navigationcompose.typed.ResultDestination

sealed interface Destinations : Destination {
	@Serializable
	object Dialog : Destinations, ResultDestination<Dialog.Result> {
		@Serializable
		data class Result(
			val something: Int,
		)
	}
}

@Composable
fun Host(navController: NavController) {
	ComposableResultEffect(navController) { result: Destinations.Dialog.Result ->
		println(result)
		// process the result
	}

	Button(
		onClick = { bavController.navigate(Destinations.Dialog) },
	) {
		Text("Open")
	}
}

About

Type-safe arguments for Jetpack Navigation Compose using Kotlinx.Serialization

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Kotlin 97.7%
  • Shell 2.3%