Pulse represents each state transition in MVI — a clear, observable signal of change.
Minimal, cross-platform-first MVI framework.
Current version: 0.2.0
Chinese README: README.zh-CN.md
- Unidirectional flow:
Intent -> Reducer -> State (+ Effect) - Clear separation of
State(replayable) andEffect(one-shot) - Core modules are platform-agnostic
- Android and Compose integrations are optional platform modules
mvi-core-contract: core contracts (MviState,MviIntent,MviEffect,Reducer,Store)mvi-core-runtime: runtime implementation (DefaultStore,StorePlugin)mvi-platform-android: AndroidViewModeladaptermvi-platform-android-compose: Compose bindings (collectStateAsState,observeEffects)mvi-extensions: optional plugins (logging, state transition tracking)
Pulse now supports split intents for complex features:
MviUiIntent: external input from UI/user actionsMviMutation: internal state transition messages consumed by reducerPulseSplitViewModel:send(uiIntent)-> executor side effects ->dispatchMutation(mutation)-> reducer
This keeps reducers pure and keeps IO/business orchestration out of mutation logic.
Ensure mavenCentral() is configured:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}Recommended for Android + Compose:
dependencies {
implementation("io.github.magic-xu:mvi-platform-android-compose:0.2.0")
}Android without Compose:
dependencies {
implementation("io.github.magic-xu:mvi-platform-android:0.2.0")
}Optional extensions:
dependencies {
implementation("io.github.magic-xu:mvi-extensions:0.2.0")
}Core-only usage:
dependencies {
implementation("io.github.magic-xu:mvi-core-runtime:0.2.0")
}The app module supports two modes:
local: depend on included project libs (default)remote: depend on Maven Central artifacts
One-click switch:
./gradlew useLocalPulseDeps
./gradlew useRemotePulseDeps
./gradlew printPulseDepModeManual override for one build:
./gradlew :app:assembleDebug -PPULSE_APP_DEP_MODE=remote
./gradlew :app:assembleDebug -PPULSE_APP_DEP_MODE=localimport com.magic.mvicore.android.PulseViewModel
import com.magic.mvicore.android.compose.collectStateAsState
import com.magic.mvicore.android.compose.observeEffects
import com.magic.mvicore.contract.MviEffect
import com.magic.mvicore.contract.MviIntent
import com.magic.mvicore.contract.MviState
import com.magic.mvicore.contract.Next
import com.magic.mvicore.contract.Reducer
data class CounterState(val count: Int = 0) : MviState
sealed interface CounterIntent : MviIntent {
data object Increase : CounterIntent
data object Decrease : CounterIntent
}
sealed interface CounterEffect : MviEffect {
data object ReachTen : CounterEffect
}
object CounterReducer : Reducer<CounterState, CounterIntent, CounterEffect> {
override fun reduce(previous: CounterState, intent: CounterIntent): Next<CounterState, CounterEffect> {
return when (intent) {
CounterIntent.Increase -> {
val next = previous.copy(count = previous.count + 1)
if (next.count == 10) Next.withEffect(next, CounterEffect.ReachTen) else Next.just(next)
}
CounterIntent.Decrease -> Next.just(previous.copy(count = previous.count - 1))
}
}
}
class CounterViewModel : PulseViewModel<CounterState, CounterIntent, CounterEffect>(
initialState = CounterState(),
reducer = CounterReducer
) {
fun increase() = dispatch(CounterIntent.Increase)
}@Composable
fun CounterScreen(viewModel: CounterViewModel) {
val state by viewModel.collectStateAsState()
viewModel.observeEffects { effect ->
when (effect) {
CounterEffect.ReachTen -> {
// show toast / navigate
}
}
}
Button(onClick = { viewModel.increase() }) {
Text("Count = ${state.count}")
}
}- GitHub: https://github.com/Magic-Xu/pulse
- Releases: https://github.com/Magic-Xu/pulse/releases
- Issues: https://github.com/Magic-Xu/pulse/issues
- API (Contract Source): https://github.com/Magic-Xu/pulse/tree/master/mvi-core-contract/src/main/kotlin/com/magic/mvicore/contract
- API (Runtime Source): https://github.com/Magic-Xu/pulse/tree/master/mvi-core-runtime/src/main/kotlin/com/magic/mvicore/runtime
- API (Android Source): https://github.com/Magic-Xu/pulse/tree/master/mvi-platform-android/src/main/java/com/magic/mvicore/android
- Consumer guide (EN): docs/CONSUMER_GUIDE.md
- Consumer guide (中文): docs/CONSUMER_GUIDE.zh-CN.md
- Iteration roadmap (EN): docs/ITERATION_ROADMAP.md
- Iteration roadmap (中文): docs/ITERATION_ROADMAP.zh-CN.md
- Release plan (EN): docs/RELEASE_PLAN.md
- Release plan (中文): docs/RELEASE_PLAN.zh-CN.md
- Maven Central publishing (EN): docs/PUBLISH_MAVEN_CENTRAL.md
- Maven Central publishing (中文): docs/PUBLISH_MAVEN_CENTRAL.zh-CN.md
Pulse v0.2 roadmap for production-scale MVI:
-
Reducer entry invariants. status: done result:
PulseViewModel+IntentExecutionScope -
Split intent channels (UI intent vs mutation). status: done result:
MviUiIntent+MviMutation+PulseSplitViewModel+UiIntentExecutor -
State decomposition toolkit. status: planned target: sub-state reducers and state-domain composition
-
Feature/store composition. status: planned target: parent-child store orchestration for complex screens
-
Effect execution middle layer. status: planned target: isolate IO/navigation/analytics from reducer
-
Debug tooling. status: planned target: intent trace + state diff timeline
-
Test DSL. status: planned target: deterministic reducer/store assertions with low boilerplate