Reaktiv is a powerful MVLI (Model-View-Logic-Intent) library for Kotlin Multiplatform, designed to simplify state management and navigation in modern applications. It provides a robust architecture for building scalable and maintainable applications across various platforms.
- MVLI Architecture: Implements a unidirectional data flow pattern with a distinct Logic layer for enhanced separation of concerns
- Kotlin Multiplatform: Supports Android, iOS, Desktop, and Web from a single codebase
- Type-safe Navigation: Powerful navigation system with nested graphs, deep linking, modals, and automatic backstack synthesis
- Jetpack Compose Integration: Seamless integration with Compose Multiplatform for declarative UI
- DevTools Support: Real-time debugging with state inspection and time-travel capabilities
- State Persistence: Built-in support for saving and restoring application state
- Coroutine-First: Built entirely on Kotlin Coroutines for efficient asynchronous operations
The foundation of Reaktiv providing the MVLI architecture components:
ModuleWithLogic<S, A, L>- Type-safe module definition with state, actions, and logicStore- Central state manager coordinating all modulesStoreAccessor- Interface for accessing state, logic, and dispatch- Middleware system for cross-cutting concerns (logging, analytics, etc.)
- State persistence with customizable strategies
CustomTypeRegistrarfor polymorphic serializationHighPriorityActionfor urgent action processingReaktivDebugutilities for development logging
Learn more about the Core module
A comprehensive type-safe navigation system:
- Nested Graph Hierarchies - Organize screens into logical groups with unlimited nesting
- Screen & Modal Support - Full modal system with dimming, backdrop handling, and layering
- Deep Linking - Automatic backstack synthesis from URL paths
- Transitions - 20+ built-in transitions with custom animation support
- Screen Layouts - Graph-level scaffolds for shared UI (app bars, bottom navigation)
- Lifecycle Callbacks -
onLifecycleCreated()withBackstackLifecyclefor visibility tracking and cleanup - Type-safe Parameters -
Paramsclass with typed access and serialization - RenderLayer System - CONTENT, GLOBAL_OVERLAY, and SYSTEM layers with z-ordering
- NotFoundScreen - Configurable fallback for undefined routes
Learn more about the Navigation module
Jetpack Compose integration for reactive UI:
StoreProvider- Provide store to Compose hierarchycomposeState<S>()- Observe module state as Compose StaterememberDispatcher()- Access dispatch functionrememberLogic<M, L>()- Access typed module logicselect<S, R>()- Derived state with custom selectorsNavigationRender- Render navigation state with animations
Learn more about the Compose module
Real-time debugging and state inspection:
- WebSocket-based connection to DevTools UI
- Action capture and replay
- State inspection and modification
- Time-travel debugging support
- Platform-aware middleware integration
Add the dependencies to your project:
// build.gradle.kts
dependencies {
implementation("io.github.syrou:reaktiv-core:<version>")
implementation("io.github.syrou:reaktiv-navigation:<version>")
implementation("io.github.syrou:reaktiv-compose:<version>")
// Optional: DevTools support
implementation("io.github.syrou:reaktiv-devtools:<version>")
}@Serializable
data class CounterState(val count: Int = 0) : ModuleState
sealed class CounterAction : ModuleAction(CounterModule::class) {
data object Increment : CounterAction()
data object Decrement : CounterAction()
data class SetCount(val value: Int) : CounterAction()
}
class CounterLogic(private val storeAccessor: StoreAccessor) : ModuleLogic<CounterAction>() {
suspend fun incrementDelayed() {
delay(1000)
storeAccessor.dispatch(CounterAction.Increment)
}
suspend fun fetchAndSetCount() {
val count = api.fetchCount()
storeAccessor.dispatch(CounterAction.SetCount(count))
}
}
object CounterModule : ModuleWithLogic<CounterState, CounterAction, CounterLogic> {
override val initialState = CounterState()
override val reducer: (CounterState, CounterAction) -> CounterState = { state, action ->
when (action) {
is CounterAction.Increment -> state.copy(count = state.count + 1)
is CounterAction.Decrement -> state.copy(count = state.count - 1)
is CounterAction.SetCount -> state.copy(count = action.value)
}
}
override val createLogic: (StoreAccessor) -> CounterLogic = { CounterLogic(it) }
}val navigationModule = createNavigationModule {
rootGraph {
startScreen(HomeScreen)
screens(ProfileScreen, SettingsScreen)
graph("auth") {
startScreen(LoginScreen)
screens(SignUpScreen)
}
}
notFoundScreen(NotFoundScreen)
}
val store = createStore {
module(CounterModule)
module(navigationModule)
middlewares(loggingMiddleware)
coroutineContext(Dispatchers.Default)
}@Composable
fun App() {
StoreProvider(store) {
NavigationRender(modifier = Modifier.fillMaxSize())
}
}
@Composable
fun CounterScreen() {
val state by composeState<CounterState>()
val dispatch = rememberDispatcher()
val logic = rememberLogic<CounterModule, CounterLogic>()
val scope = rememberCoroutineScope()
Column {
Text("Count: ${state.count}")
Button(onClick = { dispatch(CounterAction.Increment) }) {
Text("Increment")
}
Button(onClick = { scope.launch { logic.incrementDelayed() } }) {
Text("Increment Delayed")
}
}
}// Using the navigation DSL
scope.launch {
store.navigation {
navigateTo("profile") {
putString("userId", "123")
}
}
}
// Type-safe navigation
scope.launch {
store.navigation {
navigateTo<ProfileScreen> {
put("user", userObject)
}
}
}
// Deep link with backstack synthesis
scope.launch {
store.navigation {
navigateTo("auth/signup/verify", synthesizeBackstack = true)
}
}
// Pop with fallback for deep links
scope.launch {
store.navigation {
popUpTo("home", inclusive = false, fallback = "root")
}
}┌─────────────────────────────────────────────────────────────┐
│ Store │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Module A │ │ Module B │ │ NavigationModule │ │
│ │ ┌───────┐ │ │ ┌───────┐ │ │ ┌───────────────┐ │ │
│ │ │ State │ │ │ │ State │ │ │ │ NavigationState│ │ │
│ │ └───────┘ │ │ └───────┘ │ │ └───────────────┘ │ │
│ │ ┌───────┐ │ │ ┌───────┐ │ │ ┌───────────────┐ │ │
│ │ │ Logic │ │ │ │ Logic │ │ │ │NavigationLogic│ │ │
│ │ └───────┘ │ │ └───────┘ │ │ └───────────────┘ │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Middleware Chain │ │
│ │ Logging → Analytics → DevTools → ... → Reducer │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Compose UI Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ composeState│ │rememberLogic│ │ NavigationRender │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Reaktiv is released under the Apache License Version 2.0. See the LICENSE file for details.