Skip to content

ViewModel ​

Base class for business logic with automatic lifecycle management.

dart
class CounterViewModel extends ViewModel {
  late final count = state(0);
  void increment() => count.update((c) => c + 1);
}

When disposed: state holders dispose, subscriptions cancel, async tasks stop.

Structure ​

dart
class ProfileViewModel extends ViewModel {
  final ProfileRepository _repo;

  ProfileViewModel(this._repo);

  late final profile = asyncState<Profile>();
  late final isEditing = state(false);

  void loadProfile(String id) => load(profile, () => _repo.getProfile(id));
  void toggleEdit() => isEditing.update((v) => !v);
}

Dependencies ​

Pass through constructor for explicit, testable dependencies:

dart
class OrderViewModel extends ViewModel {
  final OrderRepository _repo;
  final PaymentService _payment;

  OrderViewModel(this._repo, this._payment);
}

State ​

dart
// Sync state
late final count = state(0);
late final name = state('');

// Async state
late final user = asyncState<User>();
late final items = asyncState<List<Item>>();

Streams ​

dart
// Bind directly
late final user = bind(repo.userStream, initial: null);

// Or subscribe manually
ChatViewModel(ChatRepository repo) {
  subscribe(repo.messagesStream, (msgs) => messages.value = msgs);
}

Async Operations ​

dart
// launch() — returns Task for cancellation
_task = launch((cancellation) async {
  await cancellation.wait(_repo.save(data));
  isSaved.value = true;
});

// launchWith() — callbacks
launchWith(
  (cancellation) => cancellation.wait(_repo.delete(id)),
  onSuccess: (_) => isDeleted.value = true,
  onError: (e) => error.value = e.toString(),
);

// load() — updates AsyncStateHolder
load(user, () => _repo.getUser(id));

All cancel on dispose. See Task for debouncing patterns.

Custom Cleanup ​

dart
@override
void dispose() {
  _controller.dispose();
  super.dispose();
}

Automatic Cleanup ​

ResourceOn Dispose
state() / asyncState()Disposed
bind() / bindAsync()Cancelled, disposed
subscribe()Cancelled
launch() / load()Cancelled

ViewModel Granularity ​

Prefer one ViewModel per screen or feature:

dart
// ✅ Good: Focused ViewModels
class TodoListViewModel extends ViewModel { ... }
class TodoDetailViewModel extends ViewModel { ... }
class TodoSearchViewModel extends ViewModel { ... }

// ❌ Avoid: God ViewModel
class TodosViewModel extends ViewModel {
  // list state, detail state, search state, settings...
}

Focused ViewModels are easier to test, reuse, and maintain. They also benefit from cleaner lifecycle management—each ViewModel disposes only when its specific feature is no longer needed.

Released under the MIT License.