Skip to main content

vm_result

A minimal, production-grade MVVM ViewModel contract for Flutter

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'),
);
},
)