Skip to content

Arsenoal/forgenav

Repository files navigation

ForgeNav

Opinionated navigation + offline-first MVI for Kotlin Multiplatform — Android, iOS, and JVM desktop.

Kotlin Maven Central License CI

Android iOS 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).

What’s new in 1.2.0

  • Transactional back stack writes (N-BS-12) — full stack for reads; ops/diffs applied once so hosts don’t see intermediate stacks
  • BackStackOp / BackStack.apply under 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


Why ForgeNav

Type-safe navigation — Sealed @Serializable routes, multiplatform backstack, deep links via kotlinx-serialization.

Offline-first MVIMviViewModel with optimistic updates, rollback, and optional SyncFacade binding.

Compose-firstForgeNavHost, 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 portsSyncEngine / Outbox / ConflictResolver in core; optional forgenv-syncforge adapters.

Process death readyRouteCodec + rememberSaveableForgeNavigator restore the stack.


Quick start

// 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


Platforms

Platform Entry Sample
Android Compose + rememberSaveableForgeNavigator :sample-android
iOS MainViewController() → Compose UIKit :sample-ios + iosApp/
JVM desktop Compose Desktop window :sample-desktop

Modules

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


Documentation

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

Sample apps

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 + Xcode

Related: SyncForge

ForgeNav 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

Development

Want to contribute or run the repo locally? See CONTRIBUTING.md.

git clone https://github.com/Arsenoal/forgenav.git
cd forgenav
./gradlew verifyReleaseSignOff

Optional local SyncForge composite: clone syncforge next to this repo (../syncforge); otherwise CI resolves SyncForge from Maven Central.


License

Apache License, Version 2.0

About

Opinionated KMP navigation + offline-first MVI for Compose Multiplatform, built to pair with SyncForge.

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages