Wise Talk App: Clean Architecture
Navigating the Data Flow & Project Structure
Understanding Clean Architecture
Clean Architecture organizes an application into concentric layers, promoting separation of concerns and maintainability. The most crucial principle is the Dependency Rule: dependencies can only flow inward. Inner circles (like your Domain Layer’s core business logic) should never know about outer circles (like the UI framework or specific database implementations).
In the “Wise Talk” app, this architecture ensures that core business rules remain independent of UI or data storage technologies. This document provides an interactive blueprint of this structure and illustrates how data moves through the application, making complex concepts easy to digest.
Architectural Layers & Responsibilities
The application is structured into distinct layers, each with specific responsibilities. Clicking on each layer will reveal detailed insights into its purpose and contained file types. This modular approach enhances testability, maintainability, and scalability.
Domain Layer
The innermost layer: Core business rules, entities, and abstract contracts.
Purpose:
- Contains the application’s core logic, independent of external frameworks.
- Defines the “what” of your application’s operations.
Structure (`lib/domain/`):
- `models/`: Pure Dart data structures (`WiseSaying`, `LearningSession`).
- `enums/`: Domain-specific enumerations (`ModerationStatus`).
- `failures/`: Custom error types (`Failure`).
- `repositories/`: Abstract repository interfaces (`WiseSayingRepository`).
- `use_cases/`: Abstract use case interfaces (`GetWiseSayingsUseCase`).
- `use_cases/implementations/`: Concrete use case implementations.
Data Layer
Outer layer: Handles data storage and retrieval. Concrete implementations.
Purpose:
- Responsible for how data is actually stored, retrieved, and managed.
- Implements the abstract repository interfaces from the Domain Layer.
Structure (`lib/data/`):
- `repositories/`: Concrete repository implementations (`WiseSayingRepositoryImpl`).
- `datasources/local/`: Drift database, DAOs (`wise_saying_dao.dart`), converters.
- `datasources/remote/`: Firebase, TTS, Auth data source implementations.
- `mappers/`: Extensions for data model conversions.
- `extensions/`: General data-layer extensions.
Presentation Layer
Outermost layer: The User Interface and its state management.
Purpose:
- Displays data to the user and captures user input.
- Consumes use cases from the Domain Layer.
Structure (`lib/presentation/`):
- `screens/`: UI screens (`home_screen.dart`).
- `widgets/`: Reusable UI components.
- `viewmodels/`: UI state holders (Riverpod notifiers/controllers).
Core Layer
Application-wide utilities and constants.
Purpose:
- Houses cross-cutting concerns that don’t fit into a specific architectural layer.
Structure (`lib/core/`):
- `utils/`: `app_logger.dart`, other utility functions.
- `constants/`: `app_constants.dart` (API keys, default values).
End-to-End Data Flow: Wise Sayings Example
This section illustrates a typical data flow, from user interaction in the UI down to the data sources and back. We’ll trace the process for fetching a list of wise sayings, demonstrating how each layer contributes to the operation, from the Presentation Layer’s request to the Data Layer’s orchestration of local and remote sources.
1. User Action / UI Display
A screen needs to display wise sayings. It observes a Riverpod provider for the use case.
2. Use Case Call (Domain Layer)
The UI’s ViewModel calls a `GetWiseSayingsUseCase`. This use case depends only on the abstract `WiseSayingRepository`.
3. Repository Orchestration (Data Layer)
The concrete `WiseSayingRepositoryImpl` receives the request. It triggers a background sync to Firebase and immediately returns a stream from the local Drift database.
4. Local Data Source (Drift DAO)
`WiseSayingDao` queries the local SQLite database and emits a stream of `WiseSayingEntry` objects, which are mapped to domain `WiseSaying` models.
5. Remote Data Source (Firebase)
`WiseSayingFirebaseDataSourceImpl` fetches from Firestore. It handles conversions between Firebase `Timestamp` and Dart `DateTime`, and maps raw Firestore data to `WiseSaying` models. This also feeds updates to the local cache.
6. Data Flow Back Up
The `Stream` of `Either
Key Technologies & Their Roles
The “Wise Talk” app leverages a robust set of tools, each serving a specific purpose within the Clean Architecture framework.
Dart & Flutter
The primary language and framework for cross-platform app development and UI creation.
Riverpod
Modern state management and dependency injection library, ensuring compile-time safety and testability across layers.
Dartz (Either)
Functional programming library for explicit error handling, clearly distinguishing between success (Right) and failure (Left) states.
Freezed & JSON Serializable
Code generation tools for creating immutable data models with utility methods (copyWith, equality) and efficient JSON serialization/deserialization.
Drift (SQLite)
An ORM for local SQLite database persistence, providing reactive stream capabilities for offline data and caching.
Firebase Firestore
A scalable NoSQL cloud database for storing user-generated content and enabling real-time synchronization across devices.
Cloud TTS & Translation APIs
External services integrated for high-quality text-to-speech generation and on-the-fly content translation, enhancing the learning experience.
Benefits of This Architecture
The chosen Clean Architecture, combined with the selected tools, offers significant advantages for the “Wise Talk” app’s development and long-term viability. It ensures a robust, scalable, and delightful user experience.
- Separation of Concerns: Each layer has a well-defined responsibility, preventing tight coupling and making the codebase easier to understand and manage.
- Testability: Each layer can be tested in isolation by mocking its dependencies (e.g., testing a use case by mocking its abstract repository interface). This leads to more reliable code.
- Maintainability: Changes in one layer (e.g., switching databases or UI frameworks) have minimal impact on other layers, especially the core Domain Layer.
- Scalability: The modular design simplifies adding new features, expanding content, and integrating new services without destabilizing the existing application.
- Offline-First Capabilities: Leveraging Drift for local caching, the app can provide a seamless experience even without an internet connection, syncing data when connectivity is restored.
- Reactive UI: Through Riverpod and streams from data sources, the user interface automatically updates in real-time as data changes, providing a dynamic and responsive experience.