Architecture

CNVRT follows clean architecture principles with clear separation of concerns across three main layers.

Layered Architecture

┌─────────────────────────────────────────────┐
│              UI Layer                       │
│  (Screens, Widgets, Theme)                  │
└──────────────┬──────────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────────┐
│         State Management                    │
│  (Riverpod Providers & Notifiers)           │
└──────────────┬──────────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────────┐
│          Domain Layer                       │
│  (Business Logic, Models, Constants)        │
└──────────────┬──────────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────────┐
│           IO Layer                          │
│  (Settings, Repositories, Data Sources)     │
└──────────────┬──────────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────────┐
│      Data Sources                           │
│  (Dio HTTP Client, Drift Database)          │
└─────────────────────────────────────────────┘

Layer Responsibilities

UI Layer (lib/ui/)

  • Screens and widgets organized by feature
  • Consumes Riverpod providers for reactive updates
  • Theme configuration and styling
  • User interaction handling

State Management (lib/domain/di/providers/)

  • Riverpod providers with annotation-based code generation
  • State notifiers for complex state logic
  • Selector providers for derived state
  • Feature-based organization (currencies, settings, units)

Domain Layer (lib/domain/)

  • Business models and entities
  • Application constants and configuration
  • Extension methods and utilities
  • Feature interfaces

IO Layer (lib/io/, lib/db/)

  • Repository pattern implementation
  • Settings persistence via SharedPreferences
  • Database tables and queries via Drift
  • API client configuration

Project Structure

cnvrt/
├── lib/
│   ├── domain/              # Business logic and models
│   │   ├── constants/       # App-wide constants
│   │   ├── di/              # Dependency injection and providers
│   │   │   └── providers/   # Riverpod providers by feature
│   │   ├── extensions/      # Dart extension methods
│   │   ├── io/              # Interface definitions
│   │   └── models/          # Data models and entities
│   ├── io/                  # Data persistence layer
│   │   └── settings.dart    # Settings implementation
│   ├── db/                  # Database (Drift)
│   │   ├── repos/           # Repository implementations
│   │   ├── tables/          # Drift table definitions
│   │   └── database.dart    # Database configuration
│   ├── ui/                  # User interface
│   │   ├── screens/         # Feature screens
│   │   │   ├── home/        # Home screen
│   │   │   ├── currencies/  # Currency management
│   │   │   ├── units/       # Unit conversion
│   │   │   ├── settings/    # App settings
│   │   │   ├── debug/       # Debug screens
│   │   │   └── error/       # Error handling
│   │   └── widgets/         # Reusable UI components
│   ├── l10n/                # Localization
│   ├── utils/               # Utility functions
│   ├── config/              # App configuration
│   ├── theme.dart           # Theme definitions
│   └── main.dart            # Application entry point
├── test/                    # Unit and widget tests
└── pubspec.yaml             # Dependencies

Code Style Guidelines

Naming Conventions

Type Convention Example
Classes & Widgets PascalCase CurrencyConverter
Variables & Functions camelCase fetchExchangeRates()
Files snake_case currency_converter.dart
Interfaces Prefix with 'I' ISettings
Constants lowerCamelCase defaultRefreshInterval

Code Organization

  • Imports: Grouped by functionality; package imports first, then project imports
  • Widgets: Use composition over inheritance with small, focused components
  • Error Handling: Dedicated error screens/widgets for specific scenarios
  • Types: Use strong typing with required parameters where appropriate

Linting

The project enforces code quality through analysis_options.yaml:

  • flutter_lints: Official Flutter lint rules
  • riverpod_lint: Riverpod-specific lint rules
  • custom_lint: Custom lint rule framework

Run linting with: flutter analyze

Development Commands

Command Purpose
flutter clean && flutter pub get Reset dependencies
flutter pub run build_runner build Generate code (Riverpod, Drift)
flutter analyze Run static analysis
flutter test Run all tests
flutter test test/widget_test.dart Run single test file
flutter build apk --release Build release APK
./bin/install-wireless.sh Build and install to wireless device
adb reverse tcp:3001 tcp:3001 Bridge ADB ports for API server

Build Variants

CNVRT supports two build flavors:

Standard Build

  • Includes Firebase Crashlytics for error reporting
  • Suitable for Google Play Store and Apple App Store
  • Build command: flutter build apk --flavor standard --release

FOSS Build

  • Excludes all proprietary libraries (Firebase, Google Services)
  • Fully FOSS-compliant for F-Droid distribution
  • No telemetry or remote crash reporting
  • Build command: flutter build apk --flavor foss --dart-define=FOSS_BUILD=true --release

See the FOSS Builds documentation for more details.

Localization

The app supports 5 languages:

  • 🇬🇧 English (en)
  • 🇪🇸 Spanish (es)
  • 🇮🇹 Italian (it)
  • 🇰🇷 Korean (ko)
  • 🇨🇳 Chinese (zh)

Adding Translations

  1. Edit or create ARB files in lib/l10n/ (e.g., app_en.arb, app_es.arb)
  2. Regenerate localization code: ./bin/generate-i10n.sh or flutter gen-l10n
  3. Test translations by changing device language or using the in-app selector

Data Flow

  1. UI triggers an action (e.g., fetch currencies button pressed)
  2. Provider notifier executes business logic (validates, prepares request)
  3. Repository fetches data from remote API or local database
  4. Data flows back through providers to update application state
  5. UI rebuilds reactively with new state via Riverpod watchers