The second feature reveals the architecture

A first Flutter screen can succeed with local state, direct API calls, and a few reusable widgets. The real test begins when another feature needs the same data, navigation becomes conditional, and loading or error states must behave consistently.

Architecture earns its place when it reduces the number of things a developer must understand at once. It should make a feature easy to find, change, test, and remove.

Give each layer one job

Keep widgets focused on presentation and interaction. A view model or equivalent state holder turns application data into UI state and exposes actions. Repositories become the source of truth for domain data, while services handle APIs, local storage, and platform integrations.

This separation follows Flutter's current architecture guidance, but the names matter less than the dependency direction. A widget should not need to know how an access token is refreshed or which endpoint supplied a model.

  • Views render state and forward user intent.
  • State holders coordinate UI logic and asynchronous work.
  • Repositories own data rules, caching, and source selection.
  • Services isolate external systems and platform APIs.

Model complete UI states

A screen is rarely just data. It can be idle, loading, ready, empty, refreshing, partially available, or failed. Represent those states explicitly instead of distributing booleans across the widget tree. This prevents impossible combinations and makes design review much easier.

Error state copy should explain what happened and what the user can do. Retry behavior belongs near the operation that failed, not inside a generic button with hidden side effects.

Scale structure only when pressure appears

Do not begin every app with a deep folder hierarchy and abstractions for imagined scale. Start with feature boundaries and a small shared core. Extract a pattern when at least two real features need it and their differences are understood.

A maintainable Flutter app is not the one with the most layers. It is the one where state changes have an obvious path from user action to data and back to pixels.

What I would carry into the next build

Use architecture to make state and dependencies visible. Start with feature boundaries, then extract shared patterns from real repetition.

Further reading

Flutter app architecture guide