Designed for Developer Velocity
Ditch boilerplate and build clean, reactive MVVM features in Flutter.
Zero-Boilerplate Async Guards
Wrap your network or repository fetches in run(). It handles loading, data updates, and try-catch blocks automatically.
Automatic Deduplication
Avoid API spam. Guards naturally drop concurrent duplicate execution requests, keeping your server traffic clean.
Optimistic UI Updates
Instantly update the local state for a snappier UI, with built-in automatic rollbacks if the remote operation fails.
Cancel-and-Replace Semantics
Perfect for search-as-you-type inputs. Stale async actions are safely cancelled when a newer request is triggered.
One-Shot UI Effects
Cleanly dispatch transient notifications like snackbars, toasts, or navigation without polluting persistent widget states.
Custom Logging Bridge
Pluggable logging registration. Route all ViewModel logs directly into Sentry, Crashlytics, or Talker in a single line.
Declarative state, resolved.
Stop writing custom event loops, stream controllers, or multiple loading-state variables for every screen. Let vm_result handle the lifecycles so you can focus on building features.
- Type-safe pattern matching.
- Standardized, predictable lifecycle across all features.
- Lightweight, relying on Flutter's native
ChangeNotifier.
1. Declare the ViewModel
class UserViewModel extends VMResult<User> {
UserViewModel(this._api) : super(const Result.initial());
final UserApi _api;
// Handles loading, data, error state,
// try-catch, and api-request deduplication!
Future<void> fetchUser(String id) {
return run(() => _api.getUser(id));
}
}
2. Match the State in UI
ResultBuilder<User>(
listenable: viewModel,
builder: (context, state, child) {
return state.when(
initial: () => const Text('Tap to load'),
loading: () => const CircularProgressIndicator(),
data: (user) => Text('Hello ${user.name}!'),
error: (err) => Text('Failed: $err'),
);
},
)