[{"data":1,"prerenderedAt":23},["ShallowReactive",2],{"articolo-architettura-a-livelli-in-flutter-come-strutturare-unapp-scalabile":3,"comments-article-architettura-a-livelli-in-flutter-come-strutturare-unapp-scalabile":22},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"video_url":10,"status":11,"published_at":12,"meta_title":13,"meta_description":14,"category":15,"author":19},13,"Architettura a livelli in Flutter: come strutturare un'app scalabile","architettura-a-livelli-in-flutter-come-strutturare-unapp-scalabile","Scopri come organizzare un progetto Flutter in livelli ben definiti (presentation, domain, data) per ottenere codice manutenibile, testabile e scalabile nel tempo.","## Perché serve un'architettura\n\nQuando un'app Flutter cresce, mettere tutta la logica dentro i widget diventa rapidamente ingestibile. Senza una struttura chiara, ci si ritrova con `setState` ovunque, chiamate di rete dentro i `build`, e codice impossibile da testare. Un'architettura a livelli (layered architecture) risolve questi problemi separando le responsabilità.\n\nIn questo articolo vedremo un approccio pragmatico, ispirato alla Clean Architecture, organizzato in tre livelli: **presentation**, **domain** e **data**.\n\n## I tre livelli\n\n- **Presentation**: widget, schermate e gestione dello stato della UI.\n- **Domain**: la logica di business pura, indipendente da Flutter e da qualsiasi framework esterno. Contiene entità, repository (come interfacce) e use case.\n- **Data**: implementazione concreta dei repository, sorgenti dati remote (API) e locali (database, cache), modelli (DTO).\n\nLa regola d'oro è la **dipendenza verso l'interno**: la presentation dipende dal domain, il data dipende dal domain, ma il domain non dipende da nessuno.\n\n## Struttura delle cartelle\n\nUn'organizzazione per feature mantiene il progetto leggibile:\n\n```\nlib\u002F\n  features\u002F\n    auth\u002F\n      data\u002F\n        datasources\u002F\n        models\u002F\n        repositories\u002F\n      domain\u002F\n        entities\u002F\n        repositories\u002F\n        usecases\u002F\n      presentation\u002F\n        pages\u002F\n        widgets\u002F\n        controllers\u002F\n  core\u002F\n    error\u002F\n    network\u002F\n```\n\n## Il livello Domain\n\nIniziamo dall'entità, un oggetto puro senza dipendenze esterne:\n\n```dart\nclass User {\n  final String id;\n  final String name;\n  final String email;\n\n  const User({\n    required this.id,\n    required this.name,\n    required this.email,\n  });\n}\n```\n\nDefiniamo poi l'interfaccia del repository nel domain. Nota che è un'astrazione: non sa nulla di HTTP o database.\n\n```dart\nabstract class UserRepository {\n  Future\u003CUser> getUser(String id);\n}\n```\n\nIl use case incapsula una singola azione di business:\n\n```dart\nclass GetUser {\n  final UserRepository repository;\n\n  const GetUser(this.repository);\n\n  Future\u003CUser> call(String id) {\n    return repository.getUser(id);\n  }\n}\n```\n\nL'uso del metodo `call` permette di invocare l'oggetto come fosse una funzione: `getUser(id)`.\n\n## Il livello Data\n\nIl modello (DTO) estende o mappa l'entità e gestisce la serializzazione:\n\n```dart\nclass UserModel extends User {\n  const UserModel({\n    required super.id,\n    required super.name,\n    required super.email,\n  });\n\n  factory UserModel.fromJson(Map\u003CString, dynamic> json) {\n    return UserModel(\n      id: json['id'] as String,\n      name: json['name'] as String,\n      email: json['email'] as String,\n    );\n  }\n\n  Map\u003CString, dynamic> toJson() => {\n        'id': id,\n        'name': name,\n        'email': email,\n      };\n}\n```\n\nLa data source si occupa della comunicazione con l'esterno:\n\n```dart\nabstract class UserRemoteDataSource {\n  Future\u003CUserModel> fetchUser(String id);\n}\n\nclass UserRemoteDataSourceImpl implements UserRemoteDataSource {\n  final HttpClient client;\n\n  const UserRemoteDataSourceImpl(this.client);\n\n  @override\n  Future\u003CUserModel> fetchUser(String id) async {\n    final response = await client.get('\u002Fusers\u002F$id');\n    return UserModel.fromJson(response.data);\n  }\n}\n```\n\nInfine l'implementazione del repository, che vive nel data ma rispetta il contratto del domain:\n\n```dart\nclass UserRepositoryImpl implements UserRepository {\n  final UserRemoteDataSource remoteDataSource;\n\n  const UserRepositoryImpl(this.remoteDataSource);\n\n  @override\n  Future\u003CUser> getUser(String id) {\n    return remoteDataSource.fetchUser(id);\n  }\n}\n```\n\n## Il livello Presentation\n\nLa presentation utilizza il use case senza conoscere i dettagli implementativi. Ecco un esempio con un semplice controller basato su `ChangeNotifier`:\n\n```dart\nclass UserController extends ChangeNotifier {\n  final GetUser getUser;\n\n  UserController(this.getUser);\n\n  User? user;\n  bool isLoading = false;\n  String? error;\n\n  Future\u003Cvoid> load(String id) async {\n    isLoading = true;\n    error = null;\n    notifyListeners();\n\n    try {\n      user = await getUser(id);\n    } catch (e) {\n      error = 'Impossibile caricare l\\'utente';\n    } finally {\n      isLoading = false;\n      notifyListeners();\n    }\n  }\n}\n```\n\n## I vantaggi concreti\n\n- **Testabilità**: il domain è puro Dart, testabile senza il framework Flutter. I repository si possono sostituire con mock.\n- **Manutenibilità**: cambiare la sorgente dati (da REST a GraphQL) impatta solo il livello data.\n- **Collaborazione**: team diversi possono lavorare su livelli diversi con confini chiari.\n\n## Quando NON esagerare\n\nLa Clean Architecture ha un costo in termini di boilerplate. Per un'app piccola o un prototipo, separare ogni livello con use case e modelli dedicati può essere overkill. Valuta la complessità reale del progetto: puoi adottare un approccio progressivo, partendo da repository e schermate, e introdurre i use case solo quando la logica di business cresce.\n\n## Conclusione\n\nUn'architettura a livelli ben pensata trasforma un progetto Flutter da un groviglio di widget a un sistema ordinato e scalabile. L'investimento iniziale si ripaga rapidamente quando l'app cresce e arrivano nuove funzionalità o si rende necessario un refactoring importante.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002F6f713453-ceaa-4810-b243-9a83440123c0.jpg",null,"published","2026-06-16T04:00:33+00:00","Architettura a livelli in Flutter: app scalabili","Guida pratica all'architettura a livelli in Flutter: presentation, domain e data per un'app scalabile, testabile e manutenibile.",{"id":16,"name":17,"slug":18},3,"Best practice","best-practice",{"id":20,"name":21},1,"Flutter Bot",[],1781680691398]