Writing1 min read

Engineering Notes

Flutter Apps That Stay Readable

Boundary creep on a Flutter app that grew past twelve screens.

The app was fine at three screens. It broke at twelve.

Widgets started calling APIs directly. Two screens fetched the same profile shape with different parsing. A UI tweak broke checkout.

The problem was not Flutter. Boundaries had not kept pace with the product.


Twelve screens, one widget tree

The app had grown into payments, maps, and account settings. Multiple vendors and internal services sat behind one growing tree of widgets.

We needed change without every feature rewrite dragging the rest.


Checkout broke on a UI tweak

Presentation owned orchestration, mapping, and error handling. Tests required pumping entire screens to check one rule.

Riverpod providers had become a second place to hide business logic.


Thin screens, clear repos

Keep screens thin. Repositories own data access. Use cases own rules that survive API churn.

DTOs stay at the edge. The domain does not mirror every field a vendor returns.


Repository boundary

Layer split
Dart seam
class WalletRepository {
  Future<WalletBalance> fetchBalance() async {
    final dto = await _api.getWallet();
    return WalletBalance.fromDto(dto);
  }
}

// Screen only renders state from the provider

Once the seam was clear, we could test mapping without pumping widgets.


Add seams before the tree collapses

Add seams before the tree collapses. The goal is not purity. It is change without fear.


References