Opinionated navigation + offline-first MVI for Kotlin Multiplatform — Android, iOS, and JVM desktop.
v1.2.0 on Maven Central · Release notes · tag v1.2.0
Type-safe routes, multi-stack Compose navigation (tabs, list–detail, results), MVI with optimistic updates, and first-class sync UX (pending outbox, conflicts, offline banners). v1.1.0 shipped Nav3-level product navigation; v1.2.0 adds transactional back-stack writes. Pairs cleanly with SyncForge — or any outbox-based engine via thin ports.
With SyncForge: SyncForge owns durable outbox + push/pull. ForgeNav owns navigation and presentation state that understands sync (optimistic UI, badges, conflict dialogs).
- Transactional back stack writes (N-BS-12) — full stack for reads; ops/diffs applied once so hosts don’t see intermediate stacks
BackStackOp/BackStack.applyunder the same public navigate/pop/setBackStack API- Single emission for
setBackStack, deep-link stack rebuild,popBackStack(count), popUpTo+navigate
Full notes: CHANGELOG.md · design: docs/BACKSTACK_TRANSACTIONS.md · parity: docs/NAV3_PARITY.md
Type-safe navigation — Sealed @Serializable routes, multiplatform backstack, deep links via kotlinx-serialization.
Offline-first MVI — MviViewModel with optimistic updates, rollback, and optional SyncFacade binding.
Compose-first — ForgeNavHost, tabs, list–detail, transitions, predictive/system back, Material3 sync widgets.
Nav3-level product navigation — multi-stack tabs, navigate-for-result, deep-link stack rebuild, interceptors, adaptive panes.
Engine-agnostic sync ports — SyncEngine / Outbox / ConflictResolver in core; optional forgenv-syncforge adapters.
Process death ready — RouteCodec + rememberSaveableForgeNavigator restore the stack.
// app/build.gradle.kts (or shared KMP commonMain)
dependencies {
implementation("studio.forgenav:forgenv-core:1.2.0")
implementation("studio.forgenav:forgenv-compose:1.2.0")
// optional SyncForge integration:
implementation("studio.forgenav:forgenv-syncforge:1.2.0")
// optional unit-test helpers:
// testImplementation("studio.forgenav:forgenv-testing:1.2.0")
}@Serializable
sealed interface AppRoute : Route {
@Serializable data object Home : AppRoute
@Serializable data class Detail(val id: String) : AppRoute
}
@Composable
fun App() {
val codec = remember {
RouteCodec().register("AppRoute", AppRoute.serializer()) { it is AppRoute }
}
val nav = rememberSaveableForgeNavigator(
startRoute = AppRoute.Home,
routeCodec = codec,
)
ForgeNavHost(
navigator = nav,
transitionSpec = NavTransitions.SlideHorizontal,
enableSystemBack = true,
) { entry ->
when (val route = entry.route) {
is AppRoute.Home -> HomeScreen(onOpen = { nav.navigate(AppRoute.Detail("42")) })
is AppRoute.Detail -> DetailScreen(route.id, onBack = { nav.popBackStack() })
}
}
}Version catalog:
[versions]
forgenav = "1.2.0"
[libraries]
forgenav-core = { module = "studio.forgenav:forgenv-core", version.ref = "forgenav" }
forgenav-compose = { module = "studio.forgenav:forgenv-compose", version.ref = "forgenav" }
forgenav-syncforge = { module = "studio.forgenav:forgenv-syncforge", version.ref = "forgenav" }
forgenav-testing = { module = "studio.forgenav:forgenv-testing", version.ref = "forgenav" }SyncForge (optional):
val facade = ForgeNavSync.fromSyncManager(
scope = appScope,
syncManager = syncManager,
outbox = outboxRepository,
conflictStore = conflictStore,
networkMonitor = networkMonitor,
)
// Or demo loop without a server:
val loop = ForgeNavSync.localLoop(appScope)Deeper walkthrough: docs/REQUIREMENTS.md · SyncForge side: SyncForge docs
| Platform | Entry | Sample |
|---|---|---|
| Android | Compose + rememberSaveableForgeNavigator |
:sample-android |
| iOS | MainViewController() → Compose UIKit |
:sample-ios + iosApp/ |
| JVM desktop | Compose Desktop window | :sample-desktop |
| Artifact | Role |
|---|---|
forgenv-core |
Routes, multi-stack navigator, deep links, results, interceptors, MVI, sync ports |
forgenv-compose |
ForgeNavHost, TabNavHost, ListDetailNavHost, transitions, back, sync UI |
forgenv-syncforge |
SyncForge adapters + LocalSyncForgeLoop |
forgenv-testing |
testForgeNavigator, stack assertions (no Compose UI required) |
Browse published coordinates: Maven Central — studio.forgenav
| Correctness contract | docs/REQUIREMENTS.md |
| Nav3 parity backlog | docs/NAV3_PARITY.md |
| Maven publish | docs/MAVEN_PUBLISH.md |
| Release process | docs/RELEASE.md |
| Changelog | CHANGELOG.md |
| Companion sync engine | SyncForge |
| Module | What it proves |
|---|---|
:sample-android |
Saveable nav, stack deep links, Intent helper, transitions, SyncForge loop |
:sample-desktop |
Same UI on JVM + Escape back |
:sample-ios / iosApp |
CMP framework hosted in SwiftUI |
./gradlew :sample-android:installDebug
./gradlew :sample-desktop:run
open iosApp/iosApp.xcodeproj # macOS + XcodeForgeNav is the navigation + UI state layer. For durable offline sync (outbox, transports, conflict store), use:
SyncForge — offline-first sync for Kotlin Multiplatform (studio.syncforge).
| Concern | Library |
|---|---|
| Push / pull / outbox persistence | SyncForge |
| Navigation, MVI, optimistic UI, sync chrome | ForgeNav |
Want to contribute or run the repo locally? See CONTRIBUTING.md.
git clone https://github.com/Arsenoal/forgenav.git
cd forgenav
./gradlew verifyReleaseSignOffOptional local SyncForge composite: clone syncforge next to this repo (../syncforge); otherwise CI resolves SyncForge from Maven Central.