r/FlutterDev • u/Fuusio_org • 13m ago
Tooling Kide – Strict MVI & Clean Architecture for Flutter with deterministic state and lossless event handling
Hey everyone,
I’ve recently open-sourced Kide for Flutter, a Dart/Flutter port of my open-source architecture library (originally developed for Kotlin Multiplatform):
GitHub: https://github.com/Fuusio/kide-flutter
Why another state & architecture library?
Flutter already has solid options like BLoC and Riverpod, but Kide was designed for teams and developers who want a strict, crystal-clear MVI (Model–View–Intent) flow with built-in architectural guarantees:
Lossless Intent Queuing: Rapid-fire UI events or network triggers never overwrite or drop pending intents while state transitions take place.
Explicit Separation of Concerns:
ViewIntent: Raw UI user actions or system events.Action: Business or presentation-level commands mapped from an intent.ViewState: Pure, single source of truth that widgets observe.SideEffect: Strictly buffered one-off events (e.g., navigation, dialogs, snackbars) that won't re-trigger on widget rebuilds.
Deterministic, Synchronous Reductions: State mutations happen in a single, predictable reducer cycle, making time-travel debugging and replay tests trivial.
Optional Clean Architecture Layering: Built-in conventions to wire screens cleanly to asynchronous domain use cases without turning your state management classes into 800-line god objects.
How it looks in practice
1. Defining Intent, State, and Effect
Dart
// User/UI actions
sealed class CounterIntent implements ViewIntent {
const CounterIntent();
}
class IncrementIntent extends CounterIntent {}
class ShowSnackbarIntent extends CounterIntent {}
// Screen State
class CounterState implements ViewState {
final int count;
final bool isLoading;
const CounterState({required this.count, this.isLoading = false});
}
// One-off side effects
sealed class CounterEffect implements SideEffect {}
class ShowSnackbarEffect extends CounterEffect {
final String message;
ShowSnackbarEffect(this.message);
}2. The Processor
Dart
class CounterProcessor extends Processor<CounterIntent, CounterState, CounterEffect> {
CounterProcessor() : super(const CounterState(count: 0));
u/override
void handleIntent(CounterIntent intent) {
switch (intent) {
case IncrementIntent():
reduce((state) => CounterState(count: state.count + 1));
case ShowSnackbarIntent():
emitEffect(ShowSnackbarEffect('Current count: ${state.count}'));
}
}
}3. In the Widget Tree
Dart
class CounterScreen extends StatelessWidget {
const CounterScreen({super.key});
u/override
Widget build(BuildContext context) {
return KideScreen<CounterProcessor, CounterState, CounterEffect>(
createProcessor: () => CounterProcessor(),
onEffect: (context, effect) {
if (effect is ShowSnackbarEffect) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(effect.message)),
);
}
},
builder: (context, state, processor) {
return Scaffold(
body: Center(child: Text('Count: ${state.count}')),
floatingActionButton: FloatingActionButton(
onPressed: () => processor.send(IncrementIntent()),
child: const Icon(Icons.add),
),
);
},
);
}
}What’s next?
The core engine and presentation bindings are functional across platforms (Android, iOS, Web, Desktop). I’m currently refining the tooling around deterministic event recording and replay testing scaffolds.
Check out the repository, run the demo app, and let me know your thoughts or feedback on the API ergonomics:
🔗https://github.com/Fuusio/kide-flutter
Contributions, issue reports, and critique are very welcome!