Add the dependency below to your module's build.gradle.kts
file:
Module | Version |
---|---|
tiamat | |
tiamat-koin |
sourceSets {
commonMain.dependencies {
// core library
implementation("io.github.composegears:tiamat:$version")
// Koin integration (https://github.com/InsertKoinIO/koin)
implementation("io.github.composegears:tiamat-koin:$version")
}
}
Use same dependencies in the dependencies { ... }
section
1) Define your screens in one of 3 available ways:
chaotic good (screen name eq to value name)
val Screen by navDestination<Unit> {
// content
}
chaotic neutral
val Screen = NavDestination<Unit>("ScreenName") {
// content
}
chaotic evil
object Screen : NavDestination<Unit> {
override val name: String = "ScreenName"
@Composable
override fun NavDestinationScope<Unit>.Content() {
// content
}
}
2) Create navController
val navController = rememberNavController(
startDestination = Screen,
destinations = arrayOf(
Screen,
AnotherScreen,
// ...
)
)
3) Setup navigation
Navigation(navController)
4) Navigate
val Screen by navDestination<Unit> {
val navController = navController()
Column {
Text("Screen")
Button(onClick = { navController.navigate(AnotherScreen) }){
Text("Navigate")
}
}
}
see example: App.kt
The screens in Tiamat
should be an entities (similar to composable functions)
the Args
generic define the type of data, acceptable by screen as input parameters
in the NavController:navigate
fun
val RootScreen by navDestination<Unit> {
// ...
val nc = navController()
// ...
nc.navigate(DataScreen, DataScreenArgs(1))
// ...
}
data class DataScreenArgs(val t: Int)
val DataScreen by navDestination<DataScreenArgs> {
val args = navArgs()
}
The screen content scoped in NavDestinationScope<Args>
The scope provides a number of composable functions:
navController
- provides current NavController to navigate back/furthernavArgs
- the arguments provided to this screen by NavControllr:navigate(screen, args)
funnavArgsOrNull
- same as navArgs
but provides null
if there is no data passed or if it was lostfreeArgs
- free type arguments, useful to store metadata or pass deeplink infoclearFreeArgs
- clear free type arguments (eg: clear handled deeplink info)navResult
- provide the data passed to NavControllr:back(screen, navResult)
as resultrememberViewModel
- create or provide view model scoped(linked) to current screenrememberSharedViewModel
- create or provide view model scoped(linked) to current/provided NavController
You may create NavController using one of rememberNavController
functions:
fun rememberNavController(
key: String? = null,
storageMode: StorageMode? = null,
startDestination: NavDestination<*>? = null,
destinations: Array<NavDestination<*>>,
configuration: NavController.() -> Unit = {}
)
and display as part of any composable function
@Composable
fun Content() {
val navController = rememberNavController( /*... */)
Navigation(
navController = navController,
modifier = Modifier.fillMaxSize().systemBarsPadding()
)
}
NavController will keep the screens data, view models, and states during navigation
[!IMPORTANT] The data may be cleared by system (eg: Android may clear memory)
Upon restoration state there is few cases depend on
storageMode
null
- will take parent NavController mode or ResetOnDataLoss
for root controllerStorageMode.Savable
- will store data in savable
storage (eg: Android -> Bundle)
[!IMPORTANT] Only 'Savable' types of params & args will be available to use
eg: Android - Parcelable + any bundlable primitives
StorageMode.ResetOnDataLoss
- store data in memory, allow to use any types of args & params (including lambdas). Reset nav controller upon data loss[!IMPORTANT]
Type checking has run into a recursive problem. Easiest workaround: specify types of your declarations explicitly
ide error.val SomeScreen1 by navDestination<Unit> { val navController = navController() Button( onClick = { navController.navigate(SomeScreen2) }, // << error here content = { Text("goScreen2") } ) } val SomeScreen2 by navDestination<Unit> { val navController = navController() Button( onClick = { navController.navigate(SomeScreen1) }, // << or here content = { Text("goScreen2") } ) }
Appears when it is circular initialization happen (Screen1 knows about Screen2 whot knows about Screen1 ...)
Solution: just define types of root(any in chain) screens explicitly
val SomeScreen1: NavDestination<Unit> by navDestination { /* ... */ }
[!IMPORTANT] Why is my system back button works wired with custom back handler?
While using custom back handler do not forget 2 rules 1) Always place
NavBackHandler
beforeNavigation
2) useNavigation(handleSystemBackEvent = false)
flag to disable extra back handler
Custom transition:
replace
navigationI use startDestination = null
+ LaunchEffect
\ DisposableEffect
to make start destination dynamic and see 1 frame of animation
// LaunchEffect & DisposableEffect are executed on `next` frame, so you may see 1 frame of animation
// to avoid this effect use `configuration` lambda within `rememberNavController` fun
// see DeeplinkScreen.kt
val deeplinkNavController = rememberNavController(
key = "deeplinkNavController",
startDestination = ShopScreen,
destinations = arrayOf(ShopScreen, CategoryScreen, DetailScreen)
) { // executed right after being created or restored
// we can do nav actions before 1st screen bing draw without seeing 1st frame
if (deeplink != null) {
editBackStack {
clear()
add(ShopScreen)
add(CategoryScreen, deeplink.categoryId)
}
replace(
dest = DetailScreen,
navArgs = DetailParams(deeplink.productName, deeplink.productId),
transition = navigationNone()
)
clearFreeArgs()
}
}
There is no default 'back' action on desktop
If you want to add one into the Tiamat
navigation just use the code below:
fun main() = application {
val backHandler = LocalNavBackHandler.current // < get ref to Global back handler
Window(
// ...
onKeyEvent = { // < add global key event handler
it.key == Key.Escape && it.type == KeyEventType.KeyUp && backHandler.back() // < call backHandler.back()
},
// ...
) {
App()
}
}
Tiamat-android
overrides LocalLifecycleOwner
for each destination and compatible with lifecycle-aware components
See an example of camera usage: AndroidViewLifecycleExample.kt
Nothing specific (yet)
Android: ./gradlew example:composeApp:assembleDebug
Desktop: ./gradlew example:composeApp:run
iOS: run XCode project or else use KMM plugin iOS target
Developed by ComposeGears 2024
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.