Skip to content

About

Undergraduate thesis — two Flutter apps, identical features, opposite architectures, compared by coupling, complexity and testability.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Clean Architecture vs. Spaghetti Code in Flutter

This project uses a slimmed-down version of my Clean Architecture app, simplified to keep scope fair against the spaghetti counterpart. For the full-featured app, visit flutter-clean-arch-riverpod.

This is my undergraduate thesis (Systems Analysis and Development, UNISINOS, Brazil, 2026), advised by Prof. Dr. Margrit Reni Krug and graded with distinction.

The idea

Build two Flutter apps with exactly the same functionality, but opposite architectural approaches: one following Clean Architecture + DDD, the other deliberately written as spaghetti code — no layers, no boundaries. Same features, same dependencies, same product. The only variable left is how the code is organized.

With that controlled pair in place, the thesis runs an actual analysis on top of it — static metrics, coupling, testability, a case study walking one feature change through both codebases — instead of just arguing in the abstract that "Clean Architecture is better."

What both apps do

A crypto quote tracker consuming the public Binance market data API:

  • Live quotes list, with filtering by symbol
  • Detail screen with a candlestick price chart (fl_chart)
  • Favorites
  • Offline cache fallback when there's no connection
  • Preferences: language (EN/ES/PT), theme, locally persisted settings

Same screens, same dependencies (dio, shared_preferences, auto_route, the provider package for state management), same product — the only independent variable is how the code is organized.

Live quotes list Quotes list falling back to offline cache Quote detail with candlestick chart Favorites Settings, with language and theme controls

Both apps render this identically — the UI is a controlled variable, not part of the comparison.

apps/clean apps/spaghetti
Structure domain → application → data/infrastructure → presentation, each behind explicit contracts Flat: screens/, notifiers/, models/ — UI, state, HTTP, and persistence calls freely mixed
Dependency direction Inward, towards domain (Dependency Rule) None enforced — any file can import anything
flowchart TB
    subgraph Clean["apps/clean"]
        direction TB
        P1["presentation\nscreens · providers"] --> A1["application\nuse cases"]
        A1 --> DO1["domain\nentities · repository interfaces"]
        D1["data\nrepositories_impl · mappers"] --> DO1
        I1["infrastructure\nDio · SharedPreferences"] --> D1
    end
    subgraph Spaghetti["apps/spaghetti"]
        direction TB
        S1["screens"] --- N1["notifiers"]
        N1 --- M1["models"]
        S1 -. "direct HTTP + storage calls" .- N1
    end
Loading

apps/clean here is deliberately simplified so its scope matches apps/spaghetti — see the link at the top for the fuller reference version, with patterns like a Result-based error model.

Results

Numbers as reported in the thesis (Section 5, "Resultados") — static analysis of both lib/ trees with dart_code_linter plus a small import-coupling script; see metrics/ for the raw data and how to reproduce it.

Metric Clean Spaghetti
Files (lib/) 67 22
Lines of code 3,132 1,588
Avg. lines of code per file 46.7 72.2
Avg. cyclomatic complexity per function 1.51 1.72
External-package imports (total / avg per file) 19 / 0.34 22 / 0.96
Test files 41 1 (unmodified template)
Line coverage of tested code 100% (385/385) —

Three numbers matter most here. Coupling: in the spaghetti app, files reach directly into third-party packages (dio, shared_preferences, …) roughly three times more often per file, because nothing forces that access behind an interface. Testability: the clean app has 41 test files with full coverage of the code under test, and getting there was straightforward — each layer sits behind a narrow interface, so a test mocks one boundary (a repository, a data source) instead of dragging in the rest of the app; the spaghetti app has zero real tests — not because testing spaghetti code is impossible, but because nothing is isolated enough to test without standing up the whole app around it. Complexity: average cyclomatic complexity per function is ~12% lower on the clean side, and where complexity does concentrate, it's isolated to the data/infrastructure layers (avg. CC 1.97 / 1.77 — mostly the Binance API response parsing) rather than spread across the app; in the spaghetti app it concentrates in the notifiers, which also hold UI state, business logic, and direct HTTP/storage calls.

None of this is free. The clean app has ~3× the files and ~2× the lines of code — a real upfront cost (interfaces, use cases, repositories) that the spaghetti app simply doesn't pay. For a small app with no growth or testing requirements, that cost may not be worth it.

Findings

  • Clean Architecture doesn't make a small app's inherent complexity disappear — it redistributes and organizes that complexity into isolated, individually simple pieces, regardless of the app's size.
  • The payoff is in predictability of maintenance cost over time. In the spaghetti app, cost compounds with every added feature; in the clean app, changes stay local to a layer.
  • Clean Architecture isn't a competitor to MVC/MVVM — it's a complementary structuring principle that those patterns can sit inside of.
  • It's justified for apps with room to grow, real testing requirements, and non-trivial business rules — not for prototypes, throwaway PoCs, or apps with no planned evolution.
  • 100% test coverage was easy to reach on the clean side, and that wasn't a goal chased for its own sake — it fell out of the architecture. Once business logic is isolated from dio and shared_preferences behind interfaces, there's nothing left un-mockable to skip.

Running the apps

Both are standard Flutter projects (SDK ^3.11.4, Flutter 3.44.5 via FVM).

cd apps/clean      # or apps/spaghetti
make fvm           # installs FVM and pins the Flutter version for this app
flutter pub get
flutter run

Each app also has a Makefile (make apk, make flutter-test, …) — see the app-level apps/clean/README.md for the full architecture breakdown and folder structure.

Repository layout

.
├── apps/
│   ├── clean/          # Clean Architecture + DDD implementation
│   └── spaghetti/       # Same app, unlayered
├── metrics/             # Static analysis + coupling data behind the comparison, with README
└── docs/                # Full thesis (PDF, Portuguese), defense slides (EN & PT), screenshots

License

MIT — feel free to use either codebase as a reference or starting point.

About

Undergraduate thesis — two Flutter apps, identical features, opposite architectures, compared by coupling, complexity and testability.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages