MVVM
02 / 02

MVVM: Avoiding the Massive ViewModel & When It Fits

MVVM: Avoiding the Massive ViewModel & When It Fits

The "Massive ViewModel" Anti-Pattern

A ViewModel accumulating too much unrelated logic and state becomes hard to understand, test, and maintain -- essentially reproducing the same bloat problem MVVM was meant to solve, just relocated to a different layer.

Keeping ViewModels Focused

  • Split a large screen's logic into smaller, composed ViewModels rather than one monolith.

  • Extract genuinely reusable logic into separate service/use-case classes the ViewModel delegates to.

  • Inject dependencies (repositories, services) rather than having the ViewModel construct them internally -- keeps it swappable for testing.

Exposing Async State

@Published var isLoading = false
@Published var errorMessage: String?
@Published var results: [Item] = []

func search(query: String) async {
  isLoading = true
  do {
    results = try await repository.search(query)
  } catch {
    errorMessage = error.localizedDescription
  }
  isLoading = false
}

// The View reflects each state transition automatically
// via data binding -- no manual polling needed

Redesigning a View Without Touching the ViewModel

Since the ViewModel holds no direct references to specific UI controls, a significant visual redesign can often be done by only changing the View layer -- leaving the tested ViewModel logic completely untouched.

When MVVM Might Be Overkill

For a genuinely trivial screen with minimal state, the ceremony of defining a separate ViewModel class, bindings, and commands can add more boilerplate than value -- MVVM's benefits scale with UI/state complexity, not a fixed rule to apply everywhere regardless of need.

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free