Presentation-Layer Architectural Patterns¶
These patterns answer the same question — how is the UI wired to the rest of the app? — but make different trade-offs about who owns state, who handles input, and how the View communicates with business logic.
The seven below are arranged roughly by historical lineage: MVC came first; MVP tried to make MVC's View testable; MVVM replaced manual updates with data binding; MVI/BLoC/Flux pushed toward unidirectional streams; VIPER took it furthest into strict role separation.
MVC — Model-View-Controller¶
Originated in Smalltalk-80 (1979) by Trygve Reenskaug. The View renders the Model. The Controller receives user input, mutates the Model, and tells the View to update.
flowchart LR
User -- input --> Controller
Controller -- updates --> Model
Model -- notifies --> View
View -- displays --> User
Controller -. selects .-> View
Server-side MVC vs original MVC
The MVC you see in Rails/Laravel/Spring is a server-side adaptation: the Controller returns a rendered View on each request, so there's no live "Model notifies View" loop. The original Smalltalk MVC was a desktop UI pattern with the Observer relationship intact.
When to use
- Server-rendered web apps where each request produces a fresh View.
- Apps where input handling and rendering are genuinely separate concerns.
- A reasonable default when you don't have a strong reason to pick something else.
Trade-offs
- Pro: Universally understood; nearly every web framework ships with it.
- Pro: Cleanly separates HTTP routing/input from rendering.
- Con: "Controller" tends to grow — business logic leaks into it ("fat controller" anti-pattern).
- Con: The original MVC's View↔Model coupling is hard to test on the desktop side.
MVP — Model-View-Presenter¶
A 1990s evolution of MVC for desktop UIs, popularized by Martin Fowler. The View becomes passive — it only renders what the Presenter tells it to. The Presenter holds presentation logic and is fully testable in isolation.
flowchart LR
User -- input --> View
View -- forwards events --> Presenter
Presenter -- reads/writes --> Model
Presenter -- updates --> View
View -- displays --> User
When to use
- Frameworks where the View is hard to unit-test (raw Android Activities, WinForms, GWT).
- Codebases where you want presentation logic 100 % covered by tests without UI tooling.
Trade-offs
- Pro: Presenter is plain old code — trivial to unit test.
- Pro: View is a "humble object" with no logic worth testing.
- Con: Lots of Presenter↔View interface boilerplate.
- Con: Largely superseded by MVVM in frameworks with data binding.
MVVM — Model-View-ViewModel¶
Introduced by Microsoft in 2005 for WPF (John Gossman). The ViewModel exposes observable state; the View binds to it declaratively. When state changes, the framework updates the View automatically — no manual view.setText(...) calls.
flowchart LR
User -- input --> View
View -- two-way binding --> ViewModel
ViewModel -- reads/writes --> Model
Model -- emits changes --> ViewModel
Built on Observer
MVVM is essentially the Observer pattern baked into the UI framework. The ViewModel is the Subject; the View is the Observer; the data-binding engine wires them together at compile or runtime.
When to use
- UI frameworks with first-class data binding: WPF, UWP, Jetpack Compose, Vue, Knockout, SwiftUI.
- Modern Android with
LiveData/StateFlowand Jetpack ViewModel. - Anywhere you want to eliminate the boilerplate of MVP's View↔Presenter calls.
Trade-offs
- Pro: Much less glue code than MVP — binding does the wiring.
- Pro: ViewModel has no UI dependencies, so it's still unit-testable.
- Con: Two-way binding can hide bugs and make data flow hard to trace.
- Con: Easy to leak business logic into ViewModels.
MVI — Model-View-Intent¶
Inspired by Cycle.js (André Staltz, ~2015) and adopted heavily in Android with Kotlin Flow. The View emits Intents (user actions); a reducer-like function turns them into a new immutable Model (state); the View renders that state. Strictly unidirectional.
flowchart LR
View -- Intent --> Reducer
Reducer -- new State --> Model
Model -- emits --> View
View -- renders --> User
User -- input --> View
When to use
- Reactive Android apps (Kotlin Flow, RxJava).
- UIs where you need a reproducible, snapshot-able state at every moment (debugging, time-travel).
- Teams already comfortable with stream-based programming.
Trade-offs
- Pro: State is immutable and one-way — easy to reason about and replay.
- Pro: Every screen state is a value you can log, persist, or test against.
- Con: More ceremony than MVVM (action types, reducers, state classes).
- Con: Reactive learning curve.
BLoC — Business Logic Component¶
The canonical Flutter pattern (introduced by Google at DartConf 2018). A BLoC takes Streams in, emits Streams out. The UI dispatches events; the BLoC processes them and emits new states; widgets rebuild when state changes.
flowchart LR
UI[Widget] -- Event Stream (sink) --> BLoC
BLoC -- reads/writes --> Repository
Repository -- data --> BLoC
BLoC -- State Stream (source) --> UI
BLoC is MVI in Flutter clothes
BLoC and MVI share the same DNA: events in, immutable state out, strictly unidirectional. BLoC is just the Flutter community's name and idiom for it, built on Dart Streams.
When to use
- Flutter apps of any non-trivial size — it's effectively the framework's default architecture.
- Any Dart code where Streams already feel natural.
Trade-offs
- Pro: Strong separation of UI from logic; deeply testable.
- Pro: First-class library support (
flutter_bloc,bloc_test). - Con: Boilerplate for every screen (events, states, BLoC class).
- Con: Can feel heavy for very simple screens.
Flux / Redux¶
Flux announced by Facebook in 2014; Redux simplified it in 2015 (Dan Abramov, Andrew Clark). One single store holds all app state. The View dispatches Actions; pure Reducers produce a new state; the View re-renders. Strictly unidirectional, single source of truth.
flowchart LR
View -- Action --> Dispatcher
Dispatcher -- Action --> Reducer
Reducer -- new State --> Store
Store -- subscribes --> View
User -- input --> View
When to use
- React apps with state shared across many components.
- Apps where you need time-travel debugging or replayable actions.
- Teams that want a strict, predictable mutation model.
Trade-offs
- Pro: Single source of truth makes debugging easier (Redux DevTools).
- Pro: Pure reducers are trivial to test.
- Con: Lots of boilerplate (actions, action creators, reducers, selectors). RTK and Zustand exist precisely to address this.
- Con: Overkill for small apps — local component state often suffices.
VIPER — View, Interactor, Presenter, Entity, Router¶
A strict iOS-flavored adaptation of Clean Architecture. Each module has five well-defined roles, each in its own file/class. Born from Mutual Mobile's blog post (2014) as a reaction to Massive View Controllers.
flowchart LR
View -- input --> Presenter
Presenter -- requests --> Interactor
Interactor -- reads/writes --> Entity
Interactor -- result --> Presenter
Presenter -- viewmodel --> View
Presenter -- navigate --> Router
Router -- presents --> View
| Role | Responsibility |
|---|---|
| View | Display, forward events |
| Interactor | Business logic / use cases |
| Presenter | Format data, decide what View shows |
| Entity | Plain data models |
| Router | Navigation between modules |
When to use
- Large iOS apps (10+ engineers) where strict module boundaries justify the boilerplate.
- Codebases that need consistent structure across many screens.
Trade-offs
- Pro: Crisp separation; everyone on the team writes the same shape of module.
- Pro: Each role is independently testable.
- Con: Heavy boilerplate — ~5 files per screen, even trivial ones.
- Con: Often cargo-culted into apps that don't need it.
How to Choose Between These¶
flowchart TD
Start[New UI screen] --> Q1{Server-rendered<br/>or SPA?}
Q1 -- Server-rendered --> MVC
Q1 -- SPA / Mobile --> Q2{Framework has<br/>data binding?}
Q2 -- Yes --> Q3{State shared<br/>across screens?}
Q2 -- No --> MVP
Q3 -- Yes, lots --> Redux[Flux / Redux]
Q3 -- Mostly local --> Q4{Want immutable<br/>unidirectional state?}
Q4 -- Yes --> Q5{Flutter?}
Q4 -- No --> MVVM
Q5 -- Yes --> BLoC
Q5 -- No --> MVI
VIPER is omitted from the flow on purpose: it's a structural style choice independent of these data-flow questions, usually adopted at the team level, not per-screen.