Questo documento illustra il piano di refactoring per l'applicazione Flutter "Excel Category", con l'obiettivo di trasformare l'architettura attuale (MVC + Riverpod "base") in una soluzione più robusta, manutenibile e scalabile, basata sui principi della Clean Architecture, MVVM (Model-View-ViewModel), i principi SOLID e l'adozione del Test-Driven Development (TDD).
L'applicazione attuale è funzionale e permette la lettura, il filtraggio e l'esportazione di dati da file Excel. Tuttavia, come in molti progetti iniziali, l'architettura presenta alcune limitazioni:
- Accoppiamento Elevato: Componenti di diversi strati (es. UI, Controller, Provider) sono strettamente interconnessi, rendendo difficile la modifica o la sostituzione di un componente senza impattare pesantemente gli altri.
- Difficoltà di Testabilità: La logica di business è mescolata con dettagli di framework (Flutter, Riverpod) e I/O, rendendo difficile scrivere unit test isolati e affidabili. L'assenza attuale di una suite di test è un rischio significativo per future modifiche.
- Violazioni dei Principi SOLID: Si riscontrano violazioni del Single Responsibility Principle (SRP) e del Dependency Inversion Principle (DIP), portando a classi con troppe responsabilità e dipendenze da implementazioni concrete anziché astrazioni.
- Problema della Lettura Formule Excel: L'attuale libreria di parsing Excel non valuta le formule, restituendo valori vuoti. Il refactoring permetterà di isolare e risolvere questo problema in modo pulito.
Il refactoring è motivato dalla volontà di migliorare la manutenibilità, la scalabilità, la testabilità e la robustezza dell'applicazione, trasformandola in un esempio di sviluppo software di alta qualità.
L'architettura adottata seguirà i principi della Clean Architecture, suddividendo l'applicazione in strati ben definiti:
lib/
├── main.dart
├── core/ // Elementi trasversali e condivisi (Errori, Use Cases base, Utility)
├── di_container.dart // Configurazione della Dependency Injection con Riverpod
└── features/ // Moduli/Funzionalità autonome
└── excel_processing/ // La nostra feature specifica (Elaborazione Excel)
├── data/ // Implementazioni concrete di Repository e Data Source
│ ├── datasources/
│ └── repositories/
├── domain/ // Il cuore della logica di business pura (Entities, Repositories Interfacce, Use Cases)
│ ├── entities/
│ ├── repositories/
│ └── usecases/
└── presentation/ // UI e gestione dello stato (BLoC, Pagine, Widget)
├── bloc/ // <-- Qui useremo Flutter BLoC
├── pages/
└── widgets/
-
Clean Architecture:
- Indipendenza Framework: Il Domain Layer sarà completamente agnostico a Flutter o qualsiasi libreria esterna.
- Testabilità: Ogni strato avrà responsabilità chiare, facilitando i test unitari isolati.
- Separazione delle Responsabilità: Ogni strato si occupa di un singolo "concern".
- Dipendenza Unidirezionale: Gli strati esterni dipendono da quelli interni, mai il contrario.
-
MVVM (Model-View-ViewModel) nel Presentation Layer:
- View (
pages/,widgets/): Widget Flutter che si occupano della visualizzazione e dell'interazione con l'utente. Osservano iViewModel(rappresentati dai BLoC) e inviano eventi. - ViewModel (
bloc/excel_bloc.dart): I BLoC agiranno come ViewModel, esponendo lo stato della UI e gestendo la logica di presentazione, orchestrando le chiamate aiUse Casesdel Domain Layer.
- View (
-
Principi SOLID:
- Single Responsibility Principle (SRP): Ogni classe/modulo avrà una singola ragione per cambiare. Esempi:
ExcelDataEntitysolo per la rappresentazione dei dati;ReadExcelFileUseCasesolo per leggere il file;ExcelLocalDatasourcesolo per l'interazione diretta con la libreria Excel. - Open/Closed Principle (OCP): Le entità e i moduli saranno aperti all'estensione ma chiusi alla modifica. Ad esempio, aggiungere un nuovo tipo di esportazione non dovrebbe modificare i
Use Caseesistenti, ma estendereFileExportRepositorycon una nuova implementazione. - Liskov Substitution Principle (LSP): Gli oggetti di una superclasse potranno essere sostituiti con oggetti delle sottoclassi senza alterare la correttezza del programma. (Meno evidente in questo refactoring, ma un principio guida).
- Interface Segregation Principle (ISP): I client non saranno costretti a dipendere da interfacce che non usano. I repository del Domain Layer avranno interfacce precise per le operazioni di cui hanno bisogno.
- Dependency Inversion Principle (DIP): I moduli di alto livello (es.
Use Cases) non dipenderanno da moduli di basso livello (es. implementazioni concrete dei repository), ma da astrazioni (interfacce). La Dependency Injection (con Flutter Riverpod) sarà usata per "invertire" questa dipendenza.
- Single Responsibility Principle (SRP): Ogni classe/modulo avrà una singola ragione per cambiare. Esempi:
-
Design Patterns:
- Builder: Potrebbe essere considerato per la costruzione di oggetti complessi (
ExcelDataEntityda raw data, se la logica di parsing diventa complessa). - Adapter: Essenziale nel Data Layer. La libreria
package:excel(o una nuova libreria) fungerà da "adaptee". IlExcelLocalDatasourcesarà l'adapter che converte i dati specifici della libreria nel formato dell'ExcelDataEntitydel Domain Layer. - Strategy: La logica di filtraggio e pivoting potrebbe essere implementata come diverse "strategie" che implementano un'interfaccia comune, permettendo di cambiare l'algoritmo al runtime. Questo sarà gestito all'interno dei
Use Caseso da classi helpers chiamate daiUse Cases. - Observer (Implicit con Flutter BLoC e Flutter Riverpod): I BLoC agiscono come soggetti osservabili (
Streamdi stati) e iViewsono gli osservatori che reagiscono ai cambiamenti di stato. Flutter Riverpod stesso implementa il pattern Observer per la gestione delle dipendenze e la reattività della UI. - State: Il pattern State sarà naturalmente integrato nel BLoC, dove ogni stato (
ExcelState) rappresenta un differente stato della UI (es.Loading,Loaded,Filtered,Error).
- Builder: Potrebbe essere considerato per la costruzione di oggetti complessi (
-
Test-Driven Development (TDD):
- Test-First Approach: Per ogni nuova funzionalità o refactoring di logica di business critica, verranno scritti prima i test falliti, poi il codice per farli passare, e infine il refactoring.
- Livelli di Test:
- Unit Tests: Per
Entities,Use Cases(Domain Layer),DatasourceseRepository Implementations(Data Layer), BLoC (Presentation Layer). - Widget Tests: Per i componenti UI che consumano i BLoC.
- Integration Tests (Opzionale per questo progetto): Per scenari end-to-end che coinvolgono più strati.
- Unit Tests: Per
Il refactoring sarà eseguito in modo incrementale sul branch feature/clean-architecture-excel per minimizzare i rischi e permettere progressi visibili.
-
Domain Layer - Entità (Core Business Objects):
- Definire
ExcelDataEntity(excel_data_entity.dart) come rappresentazione pura di una riga Excel. - Definire
ExcelFilterEntity(excel_filter_entity.dart) come rappresentazione pura dello stato dei criteri di filtro. - Sostituisce concettualmente:
lib/model/excel_element.darte la parte "dati" dilib/model/filters.dart.
- Definire
-
Core Layer - Gestione Errori e Usecase Base:
- Definire classi
FailureeExceptiongeneriche (core/errors/). - Definire un
UseCasebase (core/usecases/) per standardizzare le chiamate ai casi d'uso.
- Definire classi
-
Domain Layer - Interfacce Repository (Contratti):
- Definire
ExcelRepository(excel_repository.dart) con metodi astratti per le operazioni sui dati Excel (lettura, filtraggio, pivoting). - Definire
FileExportRepository(file_export_repository.dart) con metodi astratti per l'esportazione dei file.
- Definire
-
Domain Layer - Use Cases (Logica di Business Pura):
- Implementare
ReadExcelFileUseCase(usaExcelRepository). - Implementare
ApplyFiltersUseCase(usaExcelFilterEntityeExcelDataEntity). - Implementare
GetFilteredDataUseCase. - Implementare
ExportToExcelUseCaseeExportToPdfUseCase(usanoFileExportRepository). - Sostituisce concettualmente: La logica di
FileController.processExcelFile(parte business), la logica diFilterse la logica di esportazione diExcelExportController.
- Implementare
-
Data Layer - Data Sources (Interazione Esterna):
- Implementare
ExcelLocalDatasource(excel_local_datasource.dart). Qui verrà affrontato il problema delle formule Excel, esplorando librerie alternative o soluzioni specifiche per ottenere i valori calcolati. Questo datasource si occuperà della lettura "fisica" del file e della conversione in formati gestibili dal Repository. - Implementare
FileHandlerDatasource(file_handler_datasource.dart) per la gestione del salvataggio/condivisione su diverse piattaforme. - Sostituisce concettualmente: La parte di I/O di
FileControllere la gestione diFileSaver/ShareinExcelExportController.
- Implementare
-
Data Layer - Repository Implementations (Bridge tra Data e Domain):
- Implementare
ExcelRepositoryImpl(excel_repository_impl.dart) che useràExcelLocalDatasource. Questa classe si occuperà di mappare leException(dal Datasource) inFailure(per il Domain Layer). - Implementare
FileExportRepositoryImpl(file_export_repository_impl.dart) che useràFileHandlerDatasource.
- Implementare
-
Dependency Injection (Riverpod - di_container.dart):
- Configurare tutti i
Providerdi Flutter Riverpod per iniettare le dipendenze deiUse Casescon le rispettive implementazioni deiRepository, e deiRepositorycon iDatasources.
- Configurare tutti i
-
Presentation Layer - BLoC (ViewModel):
- Definire
ExcelBloc(excel_bloc.dart), i suoiExcelEvent(excel_event.dart) e i suoiExcelState(excel_state.dart) utilizzando il pacchetto flutter_bloc. Questo BLoC sarà il ViewModel che la UI osserverà. Riceverà eventi dalla UI e orchestrerà le chiamate aiUse Cases. - Sostituisce concettualmente:
ElementsProvider,FiltersProvider,ColumnTitlesProvider.
- Definire
-
Presentation Layer - Pagine e Widget (UI):
- Riprogettare
lib/features/excel_processing/presentation/pages/excel_home_page.darteexcel_details_page.dartper consumare lo stato diExcelBloce inviare eventi. - Adattare i widget esistenti (
ColumnFilterCard,RowFilters,DetailsElement,InsertFile) e spostarli inlib/features/excel_processing/presentation/widgets/, facendoli interagire con il nuovoExcelBloc. - Sostituisce direttamente:
view/home_page.dart,view/filter_details.dart, e i widget inview/home_page_widgetseview/filter_details_widgets.
- Riprogettare
-
Integrazione e Pulizia Finale:
- Aggiornare
main.dartper usare le nuove pagine. - Rimuovere gradualmente le vecchie cartelle (
lib/model,lib/control,lib/provider,lib/view) una volta che tutto il loro contenuto è stato migrato e verificato. - Assicurarsi che tutti i test passino.
- Aggiornare