
Router-style navigation featuring type-safe routing, view-model store with lifecycle handling, convenient view-model creation and retrieval, overlay destinations, custom animation/gesture system, and automatic nav-controller management.
(?)
(?)
(i dont have apple's devices) Some time in the future WASM/JS and maybe WASI.
Router style navigation library with Decompose used as a base with some features on top, like view model store, overlays, custom extensions like animations, etc.
There was existing multiplatform tooling, but it was kinda raw for my taste, so I started learning and experimenting by making DSLs based on it and this is the result.
Uhm...
In your version catalog add the "com.github.nxoim.decomposite:decomposite" artifact. In your toml file that would be:
decomposite = { module = "com.github.nxoim.decomposite:decomposite", version.ref = "version" }
First you have to set up the app by creating a root of the app. This root sets up stores for view models and nav controllers, overlay stuff, and provides the root component context.
On Android:
class YourActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// default component context is included with the decompose library
val navigationRootData = NavigationRootData(defaultComponentContext())
setContent {
NavigationRootProvider(navigationRootData) { YourContent() }
}
}
}Check out the android sample if you want predictive gesture animations application-wide and on older androids.
On everything else:
// outside compose
val navigationRootData = NavigationRootData()
// ...
// inside any composable at the root
NavigationRootProvider(navigationRootData) { YourContent() }Navigation host creation:
// creating an instance
val yourNavController = navController<YourDestinations>(startingDestination = YourDestinations.Star)
Scaffold(
bottomBar = {
GlobalSampleNavBar(onBack = { yourNavController.navigateBack() })
}
) { scaffoldPadding ->
NavHost(
yourNavController,
Modifier.padding(scaffoldPadding),
animations = {
when (currentChild) {
RootDestinations.Star -> fade() + scale()
else -> cleanSlideAndFade()
}
}
) {
when (it) { // nested hosts!
RootDestinations.Star -> StarNavHost()
RootDestinations.Heart -> HeartNavHost()
}
}
}Navigation controller usage:
// in any clickable
yourNavController.navigate(YourDestinations.Heart)
// navigate back
yourNavController.navigateBack()View model creation and usage:
@Composable
fun YourScreen() {
// get or create a view model
val vm = viewModel("optional key") { SomeViewModel(someArgument = "some text") }
// just get a view model.
val vm = getExistingViewModel<SomeViewModel>("optional key")
}
class SomeViewModel(someArgument: String) : ViewModel() {
// you can retain the view model until the app gets destroyed by overriding
// onDestroy and not calling removeFromViewModelStore
override fun onDestroy(removeFromViewModelStore: () -> Unit) {
// maybe still cancel the scope? maybe
viewModelScope.coroutineContext.cancelChildren()
}
}Back gestures on other platforms:
// this is for jvm
@OptIn(ExperimentalDecomposeApi::class)
fun main() = application {
// initialize this at the root of your app
val navigationRootData = NavigationRootData()
Window(
title = "Decomposite",
onCloseRequest = ::exitApplication,
) {
window.minimumSize = Dimension(350, 600)
SampleTheme {
// first wrap your app in a theme.
// because of material 3 quirks - surface wraps the root to fix text
// colors in overlays.
Surface {
// also since you need to initialize the component context of the app
// on your preferred platform anyway - it's ok to add decomposite to
// your entry-point/app module of the project, or combine it with your
// navigation module
// then initialize the back gesture overlay that will handle the back gestures.
// initialize it first, put NavigationRoot inside it, else overlays will not
// detect the gestures
BackGestureProviderContainer(
navigationRootData.defaultComponentContext,
content = { NavigationRootProvider(navigationRootData) { App() } }
)
}
}
}
}Or you can apply a modifier to the content you want to handle the back gestures, like:
ExampleComposable(Modifier.backGestureProvider(LocalBackDispatcher.current))
(?)
(?)
(i dont have apple's devices) Some time in the future WASM/JS and maybe WASI.
Router style navigation library with Decompose used as a base with some features on top, like view model store, overlays, custom extensions like animations, etc.
There was existing multiplatform tooling, but it was kinda raw for my taste, so I started learning and experimenting by making DSLs based on it and this is the result.
Uhm...
In your version catalog add the "com.github.nxoim.decomposite:decomposite" artifact. In your toml file that would be:
decomposite = { module = "com.github.nxoim.decomposite:decomposite", version.ref = "version" }
First you have to set up the app by creating a root of the app. This root sets up stores for view models and nav controllers, overlay stuff, and provides the root component context.
On Android:
class YourActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// default component context is included with the decompose library
val navigationRootData = NavigationRootData(defaultComponentContext())
setContent {
NavigationRootProvider(navigationRootData) { YourContent() }
}
}
}Check out the android sample if you want predictive gesture animations application-wide and on older androids.
On everything else:
// outside compose
val navigationRootData = NavigationRootData()
// ...
// inside any composable at the root
NavigationRootProvider(navigationRootData) { YourContent() }Navigation host creation:
// creating an instance
val yourNavController = navController<YourDestinations>(startingDestination = YourDestinations.Star)
Scaffold(
bottomBar = {
GlobalSampleNavBar(onBack = { yourNavController.navigateBack() })
}
) { scaffoldPadding ->
NavHost(
yourNavController,
Modifier.padding(scaffoldPadding),
animations = {
when (currentChild) {
RootDestinations.Star -> fade() + scale()
else -> cleanSlideAndFade()
}
}
) {
when (it) { // nested hosts!
RootDestinations.Star -> StarNavHost()
RootDestinations.Heart -> HeartNavHost()
}
}
}Navigation controller usage:
// in any clickable
yourNavController.navigate(YourDestinations.Heart)
// navigate back
yourNavController.navigateBack()View model creation and usage:
@Composable
fun YourScreen() {
// get or create a view model
val vm = viewModel("optional key") { SomeViewModel(someArgument = "some text") }
// just get a view model.
val vm = getExistingViewModel<SomeViewModel>("optional key")
}
class SomeViewModel(someArgument: String) : ViewModel() {
// you can retain the view model until the app gets destroyed by overriding
// onDestroy and not calling removeFromViewModelStore
override fun onDestroy(removeFromViewModelStore: () -> Unit) {
// maybe still cancel the scope? maybe
viewModelScope.coroutineContext.cancelChildren()
}
}Back gestures on other platforms:
// this is for jvm
@OptIn(ExperimentalDecomposeApi::class)
fun main() = application {
// initialize this at the root of your app
val navigationRootData = NavigationRootData()
Window(
title = "Decomposite",
onCloseRequest = ::exitApplication,
) {
window.minimumSize = Dimension(350, 600)
SampleTheme {
// first wrap your app in a theme.
// because of material 3 quirks - surface wraps the root to fix text
// colors in overlays.
Surface {
// also since you need to initialize the component context of the app
// on your preferred platform anyway - it's ok to add decomposite to
// your entry-point/app module of the project, or combine it with your
// navigation module
// then initialize the back gesture overlay that will handle the back gestures.
// initialize it first, put NavigationRoot inside it, else overlays will not
// detect the gestures
BackGestureProviderContainer(
navigationRootData.defaultComponentContext,
content = { NavigationRootProvider(navigationRootData) { App() } }
)
}
}
}
}Or you can apply a modifier to the content you want to handle the back gestures, like:
ExampleComposable(Modifier.backGestureProvider(LocalBackDispatcher.current))