This document outlines the refactoring plan for the Flutter application "Excel Category," aiming to transform the current architecture (MVC + basic Riverpod) into a more robust, maintainable, and scalable solution, based on Clean Architecture, MVVM (Model-View-ViewModel), SOLID principles, and the adoption of Test-Driven Development (TDD).
The current application is functional and allows reading, filtering, and exporting data from Excel files. However, like many initial projects, the architecture presents some limitations:
- High Coupling: Components from different layers (e.g., UI, Controller, Provider) are tightly interconnected, making it difficult to modify or replace one component without significantly impacting others.
- Difficulty in Testability: Business logic is intertwined with framework details (Flutter, Riverpod) and I/O operations, making it challenging to write isolated and reliable unit tests. The current lack of a test suite poses a significant risk for future modifications.
- Violations of SOLID Principles: Violations of the Single Responsibility Principle (SRP) and the Dependency Inversion Principle (DIP) are present, leading to classes with too many responsibilities and dependencies on concrete implementations rather than abstractions.
- Excel Formula Reading Issue: The current Excel parsing library does not evaluate formulas, resulting in empty values. Refactoring will allow isolating and resolving this issue cleanly.
The refactoring is motivated by the desire to enhance the application's maintainability, scalability, testability, and robustness, transforming it into an example of high-quality software development.
The adopted architecture will follow the principles of Clean Architecture, dividing the application into well-defined layers:
lib/
├── main.dart
├── core/ // Cross-cutting and shared elements (Errors, Base Use Cases, Utilities)
├── di_container.dart // Dependency Injection configuration with Riverpod
└── features/ // Autonomous modules/features
└── excel_processing/ // Our specific feature (Excel Processing)
├── data/ // Concrete Repository and Data Source implementations
│ ├── datasources/
│ └── repositories/
├── domain/ // The core of pure business logic (Entities, Repository Interfaces, Use Cases)
│ ├── entities/
│ ├── repositories/
│ └── usecases/
└── presentation/ // UI and state management (BLoC, Pages, Widgets)
├── bloc/ // <-- Here we will use Flutter BLoC
├── pages/
└── widgets/
-
Clean Architecture:
- Framework Independence: The Domain Layer will be completely agnostic to Flutter or any external libraries.
- Testability: Each layer will have clear responsibilities, facilitating isolated unit tests.
- Separation of Concerns: Each layer addresses a single "concern."
- Unidirectional Dependency: Outer layers depend on inner layers, never the other way around.
-
MVVM (Model-View-ViewModel) in the Presentation Layer:
- View (
pages/,widgets/): Flutter widgets responsible for displaying and interacting with the user. They observe theViewModel(represented by BLoCs) and dispatch events. - ViewModel (
bloc/excel_bloc.dart): The BLoCs will act as ViewModels, exposing UI state and managing presentation logic, orchestrating calls toUse Casesfrom the Domain Layer.
- View (
-
SOLID Principles:
- Single Responsibility Principle (SRP): Each class/module will have only one reason to change. Examples:
ExcelDataEntitysolely for data representation;ReadExcelFileUseCasesolely for reading the file;ExcelLocalDatasourcesolely for direct interaction with the Excel library. - Open/Closed Principle (OCP): Entities and modules will be open for extension but closed for modification. For example, adding a new export type should not modify existing
Use Cases, but rather extendFileExportRepositorywith a new implementation. - Liskov Substitution Principle (LSP): Objects of a superclass can be replaced with objects of subclasses without altering the correctness of the program. (Less evident in this refactoring, but a guiding principle).
- Interface Segregation Principle (ISP): Clients will not be forced to depend on interfaces they do not use. Domain Layer repositories will have precise interfaces for the operations they require.
- Dependency Inversion Principle (DIP): High-level modules (e.g.,
Use Cases) will not depend on low-level modules (e.g., concrete repository implementations), but on abstractions (interfaces). Dependency Injection (with Flutter Riverpod) will be used to "invert" this dependency.
- Single Responsibility Principle (SRP): Each class/module will have only one reason to change. Examples:
-
Design Patterns:
- Builder: Could be considered for constructing complex objects (
ExcelDataEntityfrom raw data, if parsing logic becomes complex). - Adapter: Essential in the Data Layer. The
package:excellibrary (or a new library) will serve as the "adaptee." TheExcelLocalDatasourcewill be the adapter that converts library-specific data into theExcelDataEntityformat of the Domain Layer. - Strategy: Filtering and pivoting logic could be implemented as different "strategies" that implement a common interface, allowing for runtime algorithm changes. This will be managed within
Use Casesor by helper classes called byUse Cases. - Observer (Implicit with Flutter BLoC and Flutter Riverpod): BLoCs act as observable subjects (Stream of states), and
Viewsare the observers that react to state changes. Flutter Riverpod itself implements the Observer pattern for dependency management and UI reactivity. - State: The State pattern will be naturally integrated into the BLoC, where each state (
ExcelState) represents a different UI state (e.g.,Loading,Loaded,Filtered,Error).
- Builder: Could be considered for constructing complex objects (
-
Test-Driven Development (TDD):
- Test-First Approach: For every new feature or critical business logic refactoring, failing tests will be written first, then the code to make them pass, and finally the refactoring.
- Test Levels:
- Unit Tests: For
Entities,Use Cases(Domain Layer),DatasourcesandRepository Implementations(Data Layer), BLoCs (Presentation Layer). - Widget Tests: For UI components that consume BLoCs.
- Integration Tests (Optional for this project): For end-to-end scenarios involving multiple layers.
- Unit Tests: For
Refactoring will be performed incrementally on the feature/clean-architecture-excel branch to minimize risks and allow for visible progress.
-
Domain Layer - Entities (Core Business Objects):
- Define
ExcelDataEntity(excel_data_entity.dart) as a pure representation of an Excel row. - Define
ExcelFilterEntity(excel_filter_entity.dart) as a pure representation of the filter criteria's state. - Conceptually replaces:
lib/model/excel_element.dartand the "data" part oflib/model/filters.dart.
- Define
-
Core Layer - Error Handling and Base Use Case:
- Define generic
FailureandExceptionclasses (core/errors/). - Define a base
UseCase(core/usecases/) to standardize use case calls.
- Define generic
-
Domain Layer - Repository Interfaces (Contracts):
- Define
ExcelRepository(excel_repository.dart) with abstract methods for Excel data operations (reading, filtering, pivoting). - Define
FileExportRepository(file_export_repository.dart) with abstract methods for file export operations.
- Define
-
Domain Layer - Use Cases (Pure Business Logic):
- Implement
ReadExcelFileUseCase(usesExcelRepository). - Implement
ApplyFiltersUseCase(usesExcelFilterEntityandExcelDataEntity). - Implement
GetFilteredDataUseCase. - Implement
ExportToExcelUseCaseandExportToPdfUseCase(useFileExportRepository). - Conceptually replaces: The business logic part of
FileController.processExcelFile, the logic ofFilters, and the export logic ofExcelExportController.
- Implement
-
Data Layer - Data Sources (External Interaction):
- Implement
ExcelLocalDatasource(excel_local_datasource.dart). This is where the Excel formula issue will be addressed, by exploring alternative libraries or specific solutions to obtain calculated values. This datasource will handle the "physical" file reading and conversion into formats consumable by the Repository. - Implement
FileHandlerDatasource(file_handler_datasource.dart) for handling file saving/sharing across different platforms. - Conceptually replaces: The I/O part of
FileControllerand theFileSaver/Sharehandling inExcelExportController.
- Implement
-
Data Layer - Repository Implementations (Bridge between Data and Domain):
- Implement
ExcelRepositoryImpl(excel_repository_impl.dart) which will useExcelLocalDatasource. This class will be responsible for mappingException(from the Datasource) intoFailure(for the Domain Layer). - Implement
FileExportRepositoryImpl(file_export_repository_impl.dart) which will useFileHandlerDatasource.
- Implement
-
Dependency Injection (Flutter Riverpod - di_container.dart):
- Configure all Flutter Riverpod
Providers to inject the dependencies ofUse Caseswith their respectiveRepositoryimplementations, andRepositorywithDatasources.
- Configure all Flutter Riverpod
-
Presentation Layer - BLoC (ViewModel):
- Define
ExcelBloc(excel_bloc.dart), itsExcelEvent(excel_event.dart), and itsExcelState(excel_state.dart) using the flutter_bloc package. This BLoC will be the ViewModel that the UI observes. It will receive events from the UI and orchestrate calls toUse Cases. - Conceptually replaces:
ElementsProvider,FiltersProvider,ColumnTitlesProvider.
- Define
-
Presentation Layer - Pages and Widgets (UI):
- Redesign
lib/features/excel_processing/presentation/pages/excel_home_page.dartandexcel_details_page.dartto consumeExcelBloc's state and dispatch events. - Adapt existing widgets (
ColumnFilterCard,RowFilters,DetailsElement,InsertFile) and move them tolib/features/excel_processing/presentation/widgets/, making them interact with the newExcelBloc. - Directly replaces:
view/home_page.dart,view/filter_details.dart, and the widgets inview/home_page_widgetsandview/filter_details_widgets.
- Redesign
-
Integration and Final Cleanup:
- Update
main.dartto use the new pages. - Gradually remove old folders (
lib/model,lib/control,lib/provider,lib/view) once all their content has been migrated and verified. - Ensure all tests pass.
- Update