A Flutter application that consolidates scattered educational resources for students preparing for high school, college, and international examinations to enhance learning efficiency.
Abugida is a mobile application built with the MVVM (Model-View-ViewModel) architecture pattern. It consolidates scattered educational resources for students preparing for high school, college, and international examinations to enhance learning efficiency.
Key highlights:
- MVVM Architecture — Clean separation of UI, Data, and Domain layers
- Unidirectional Data Flow — Predictable state management with
provider+ChangeNotifier - Offline-First Data Layer — Local SQLite caching via
drift&powersyncwith secure file storage - Optional Use Cases — Pure Dart business operations for orchestration & shared rules; simple flows can call Repositories directly
- Comprehensive Testing — Fakes pattern, unit tests, and widget tests with 80%+ coverage
- Feature-First Structure — Organized by feature in UI, by type in data and domain
- Immutable Models —
freezed-powered domain models with code generation
Abugida follows a strict 3-layer MVVM architecture with unidirectional data flow. The Domain layer sits at the core, containing pure Dart business logic with zero Flutter dependencies.
graph TD
subgraph "UI Layer — lib/ui/"
V[View / Screen] -->|Commands| VM[ViewModel<br/>ChangeNotifier]
VM -->|State| V
end
subgraph "Domain Layer — lib/domain/"
UC[Use Cases<br/>Business Operations]
M[Domain Models<br/>@freezed]
UC -->|reads/writes| M
end
subgraph "Data Layer — lib/data/"
R[Repository<br/>Source of Truth] -->|delegates| S[Service<br/>Stateless API]
S -->|returns Data Model| R
R -->|reads/writes| DB[(Local DB<br/>Drift/PowerSync)]
R -->|coordinates| FS[FileStorageService<br/>S3 + AES + SHA-256]
FS -->|secure files| Disk[(Secure Disk<br/>Encrypted Files)]
end
VM -->|calls use case - default| UC
UC -->|calls| R
UC -->|returns Domain Models| VM
VM -.->|optional: direct fetch for simple flows| R
R -->|maps to Domain Models| M
R -->|reads/writes| M
VM -->|exposes| M
V -->|renders| M
Data flow:
- User interacts with the View → emits a Command (e.g.,
onRefresh) - ViewModel calls a Use Case → the Use Case executes the business operation and calls the Repository. Use cases are optional — they are the default path for business orchestration, but a simple screen-specific fetch that only delegates to one repository method can call the Repository directly.
- Repository coordinates Services & Local DB → Services return data-layer models (raw
Map/DTO) — never domain models; the Repository maps them to domain models → returns viaEither<Error, T>→ notifies listeners - View rebuilds via
Consumer/context.watch
See ARCHITECTURE.md for the full architecture guide.
- Flutter SDK 3.22 or higher
- Dart SDK 3.6 or higher
- A code editor (VS Code with Flutter extension or Android Studio)
# 1. Clone the repository
git clone https://github.com/mortymith/Abugida-Frontend.git
cd abugida-frontend
# 2. Install dependencies
flutter pub get
# 3. Generate freezed/json_serializable/drift code
flutter pub run build_runner build --delete-conflicting-outputs
# 4. Run the app
flutter run
# 5. Run tests
flutter test
# 6. Run with coverage
flutter test --coverageInstall and generate:
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputsThis produces the *.freezed.dart and *.g.dart part files referenced by every model. They are intentionally not included in the repository and are generated during the build step.
| Document | Description | Link |
|---|---|---|
| Architecture Guide | Full MVVM architecture, layer definitions, diagrams | ARCHITECTURE.md |
| AI Agent Instructions | Rules for AI coding assistants working on this project | AGENT.md |
| Contributing Guide | How to propose features, submit PRs, code review checklist | CONTRIBUTING.md |
| docs/ | Detailed topic-specific guides | docs/ |
abugida-frontend/
├── lib/ # Application Source Code
│ ├── ui/ # UI Layer — Presentation
│ │ ├── core/ # Global UI Components
│ │ │ ├── widgets/ # Shared Widgets Library
│ │ │ └── themes/ # Theme Configuration
│ │ └── <feature_name>/ # Feature Modules
│ │ ├── view_models/ # Feature Logic Layer
│ │ │ └── <view_model_class>.dart
│ │ └── widgets/ # Feature UI Components
│ │ ├── <feature_name>_screen.dart
│ │ └── <other_widgets>
│ ├── domain/ # Domain Layer — Business Logic Core
│ │ ├── models/ # Core Business Entities (@freezed)
│ │ │ └── <model_name>.dart
│ │ └── use_cases/ # Single-Purpose Business Operations
│ │ └── <operation_name>_use_case.dart
│ ├── data/ # Data Layer — Data Access & Local Storage
│ │ ├── local/ # Local Database (Drift / PowerSync)
│ │ │ ├── tables/ # Table Definitions (split by domain)
│ │ │ ├── daos/ # Data Access Objects (Complex queries & joins)
│ │ │ └── app_database.dart # Main AppDatabase class & migration strategy
│ │ ├── repositories/ # Repository Interfaces (Source of Truth)
│ │ ├── services/ # External Service Integration & Storage
│ │ │ ├── api_service.dart # Dio HTTP wrapper
│ │ │ ├── sync_service.dart # PowerSync / remote sync orchestration
│ │ │ └── file_storage_service.dart # Secure offline file storage (S3, AES, SHA-256)
│ │ └── models/ # Data Layer Models (DTOs)
│ │ └── <api_model_class>.dart
│ ├── config/ # Application Configuration
│ ├── utils/ # Shared Utilities
│ ├── routing/ # Navigation Management
│ ├── main_staging.dart # Staging Environment Entry Point
│ ├── main_development.dart # Development Environment Entry Point
│ └── main.dart # Production Entry Point
├── test/ # Test Suite
│ ├── data/ # Data Layer Tests
│ ├── domain/ # Domain Layer Tests
│ ├── ui/ # Presentation Layer Tests
│ └── utils/ # Utility Tests
├── testing/ # Testing Infrastructure
│ ├── fakes/ # Fake Implementations
│ └── models/ # Test Data Models
├── assets/ # Static Resources
│ ├── animations/ # Motion Design Assets
│ ├── fonts/ # Custom Typography
│ └── images/ # Visual Assets
│ ├── icons/ # Iconography
│ └── illustrations/ # Decorative Graphics
├── docs/ # Documentation
│ ├── architecture/ # Architecture Guides (01-08)
│ ├── guides/ # Practical How-to Guides
│ ├── templates/ # Dart Code Templates
│ ├── cheat-sheets/ # Quick Reference Sheets
│ ├── adr/ # Architecture Decision Records
│ └── onboarding/ # Developer Onboarding Docs
├── UI/ # HTML Design Mockups
│ ├── course.html # Course Page Wireframe
│ ├── courseDetailes.html # Course Details Wireframe
│ ├── examtype.html # Exam Type Selection Wireframe
│ ├── export.html # Export Data Wireframe
│ ├── LearnTab.html # Learn Tab Wireframe
│ ├── profile.html # Profile Page Wireframe
│ ├── progress.html # Progress Tracking Wireframe
│ └── search.html # Search Page Wireframe
├── ARCHITECTURE.md # Architecture Guide
├── AGENT.md # AI Agent Instructions
├── CONTRIBUTING.md # Contribution Guidelines
├── LICENSE # MIT License
├── README.md # Project Documentation
└── pubspec.yaml # Dependencies & Metadata
| Layer | Directory | Responsibility |
|---|---|---|
| UI | lib/ui/ |
Screens (widgets), ViewModels (state + commands), and shared components. Views are pure StatelessWidget that compose widgets and bind to ViewModel state. ViewModels hold transient UI state, expose commands, and coordinate domain use cases (or call Repositories directly for simple flows). |
| Data | lib/data/ |
Repositories (source of truth with caching and retry logic) map data-layer models to domain models; Local DB (Drift tables, DAOs, and migrations for offline-first data); Services (stateless wrappers returning data-layer models — never domain models); ApiModels (DTOs for JSON serialization). |
| Domain | lib/domain/ |
Pure Dart models (immutable via @freezed) and single-purpose use cases for business orchestration. No Flutter imports allowed. This is the core of the application — all other layers depend on it, but it depends on nothing. Use cases are optional; simple flows may bypass them. |
lib/domain/use_cases/ contains single-purpose business operations that sit between the ViewModels (UI layer) and the Repositories (data layer). They allow the domain layer to expose application logic without leaking repository implementations to the UI. No Flutter imports allowed — like the models, they are pure Dart.
Use cases are optional, not mandatory. Reach for them when a flow carries real business logic — orchestration of multiple repositories, pure computation (e.g. filtering, grading), or rules shared across screens. For simple, screen-specific fetches/commands that only delegate one-for-one into a single repository method, the ViewModel may call the Repository directly. Never call a Service from a ViewModel, and never bypass the Repository.
- Name as verbs —
<operation>_use_case.dart(e.g.,grade_quiz_use_case.dart). - Single public entry point — expose one
execute()method (plus thin helper methods when needed). - Immutability — inputs are never mutated; results are immutable data objects or domain models.
- Injectable — repositories are injected via constructors (constructor injection), making use cases trivially faked in tests.
- Pure actions —
Filter*and validation use cases are plain, deterministic functions;Get*/coordinator use cases are the only ones touching repositories.
| Tool | Version | Install |
|---|---|---|
| Flutter | 3.22+ | flutter.dev |
| Dart | 3.6+ | Included with Flutter |
| IDE | VS Code or Android Studio | Install Flutter extension |
- VS Code: Install the "Flutter" and "Dart" extensions. Set
dart.flutterSdkPathin settings. - Recommended: Enable "Save on format" — auto-runs
dart formaton save.
After modifying any @freezed, @JsonSerializable, or drift classes:
# One-time build
flutter pub run build_runner build --delete-conflicting-outputs
# Watch mode (auto-regenerate on save)
flutter pub run build_runner watch --delete-conflicting-outputs# Run all tests
flutter test
# Run specific test file
flutter test test/ui/home/home_view_model_test.dart
# Run only unit tests (no widget tests)
flutter test test/ui/ test/data/
# Run only widget tests
flutter test test/ui/
# Run with verbose output
flutter test --reporter expanded
# Generate coverage report
flutter test --coverage
# Open coverage/lcov.html in a browser to viewtesting/
├── fakes/ # Shared Fake* classes
│ ├── fake_subject_repository.dart
│ └── fake_api_client.dart
└── models/
└── test_data_models.dart # Test data factories and fixtures
- Team standard coverage: 80% on all new code
- Exclusions: Generated code (
*.g.dart,*.freezed.dart) is excluded from coverage
| Package | Purpose | Version |
|---|---|---|
provider |
State management & dependency injection | ^6.0.5 |
go_router |
Declarative navigation & deep linking | ^17.3.0 |
freezed |
Immutable models with code generation | ^4.0.0-dev.3 |
freezed_annotation |
Annotations for freezed models | ^3.1.0 |
json_annotation |
JSON serialization annotations | ^4.9.0 |
json_serializable |
JSON serialization code generation | ^6.8.0 |
dartz |
Functional programming (Either for error handling) |
^0.10.1 |
dio |
HTTP client for API calls | ^5.3.2 |
drift |
Local SQLite database & offline-first data layer | ^2.34.2 |
powersync |
Real-time sync & offline-first database engine | ^2.3.2 |
crypto |
SHA-256 checksum verification for file integrity | ^3.0.3 |
encrypt |
AES-256 encryption for secure offline file storage | ^5.0.3 |
path_provider |
Access to sandboxed local file system directories | ^2.1.1 |
logger |
Structured logging | ^2.5.0 |
flutter_test |
Flutter testing framework | SDK |
flutter_lints |
Lint rules for Dart/Flutter | ^6.0.0 |
We welcome contributions! Please read our Contributing Guide before submitting a pull request.
Quick checklist:
- Follow MVVM architecture (no logic in widgets)
- Add unit tests for ViewModels & Repositories
- Add widget tests for new Screens
- Use Fakes (not Mocks) for test doubles
- Run
dart analyze .— zero issues - Run
dart format .— clean formatting
This project is licensed under the MIT License. See the LICENSE file for the full license text.