12 KiB
App Boilerplate
A modular, offline-first Flutter boilerplate for apps that store, sync, and share media and metadata across centralized (Immich, Firebase) and decentralized (Nostr) systems.
Phase 7 - Firebase Layer
- Optional Firebase integration for cloud sync, storage, auth, push notifications, and analytics
- Modular design - can be enabled or disabled without affecting other modules
- Offline-first behavior maintained when Firebase is disabled
- Integration with session management and local storage
- Comprehensive unit tests
Phase 6 - User Session Management
- User login, logout, and session switching
- Per-user data isolation with separate storage paths
- Cache clearing on logout
- Integration with local storage and sync engine
- Comprehensive unit tests
Phase 5 - Relay Management UI
- User interface for managing Nostr relays
- View, add, remove, and monitor relay health
- Manual sync trigger integration
- Modular controller-based architecture
- Comprehensive UI tests
Phase 4 - Sync Engine
- Coordinates data synchronization between local storage, Immich, and Nostr
- Conflict resolution strategies (useLocal, useRemote, useLatest, merge)
- Offline queue with automatic retry
- Priority-based operation processing
- Comprehensive unit and integration tests
Phase 3 - Nostr Integration
- Nostr protocol service for decentralized metadata synchronization
- Keypair generation and event publishing
- Multi-relay support for metadata syncing
- Comprehensive unit tests
Phase 2 - Immich Integration
- Immich API service for uploading and fetching images
- Automatic metadata storage in local database
- Offline-first behavior with local caching
- Comprehensive unit tests
Phase 1 - Local Storage & Caching
- Local storage service with SQLite database
- CRUD operations for items
- Image caching functionality
- Comprehensive unit tests
Quick Start
# Install dependencies
flutter pub get
# Run tests
flutter test
# Run app
flutter run
Local Storage & Caching
Service for local storage and caching operations. Provides CRUD operations for items stored in SQLite, image caching with automatic download/storage, and cache hit/miss handling. Modular design with no UI dependencies.
Files:
lib/data/local/local_storage_service.dart- Main service classlib/data/local/models/item.dart- Item data modeltest/data/local/local_storage_service_test.dart- Unit tests
Key Methods: initialize(), insertItem(), getItem(), getAllItems(), updateItem(), deleteItem(), getCachedImage(), clearImageCache(), close()
Immich Integration
Service for interacting with Immich API. Uploads images, fetches asset lists, and automatically stores metadata in local database. Offline-first design allows access to cached metadata without network.
Configuration: Edit lib/config/config_loader.dart to set immichBaseUrl and immichApiKey for dev/prod environments (lines 40-41 for dev, lines 47-48 for prod).
Files:
lib/data/immich/immich_service.dart- Main service classlib/data/immich/models/immich_asset.dart- Asset modellib/data/immich/models/upload_response.dart- Upload response modeltest/data/immich/immich_service_test.dart- Unit tests
Key Methods: uploadImage(), fetchAssets(), getCachedAsset(), getCachedAssets()
Nostr Integration
Service for decentralized metadata synchronization using Nostr protocol. Generates keypairs, publishes events, and syncs metadata across multiple relays. Modular design allows testing without real relay connections.
Files:
lib/data/nostr/nostr_service.dart- Main service classlib/data/nostr/models/nostr_keypair.dart- Keypair modellib/data/nostr/models/nostr_event.dart- Event modellib/data/nostr/models/nostr_relay.dart- Relay modeltest/data/nostr/nostr_service_test.dart- Unit tests
Key Methods: generateKeyPair(), addRelay(), connectRelay(), publishEvent(), syncMetadata(), dispose()
Sync Engine
Engine for coordinating data synchronization between local storage, Immich, and Nostr. Handles conflict resolution, offline queuing, and automatic retries. Processes operations by priority with configurable conflict resolution strategies.
Files:
lib/data/sync/sync_engine.dart- Main sync engine classlib/data/sync/models/sync_status.dart- Status and priority enumslib/data/sync/models/sync_operation.dart- Operation modeltest/data/sync/sync_engine_test.dart- Unit and integration tests
Key Methods: syncToImmich(), syncFromImmich(), syncToNostr(), syncAll(), queueOperation(), resolveConflict(), getPendingOperations()
Conflict Resolution: useLocal, useRemote, useLatest, merge - set via setConflictResolution()
Relay Management UI
User interface for managing Nostr relays. View configured relays, add/remove relays, monitor connection health, and trigger manual syncs. Modular design with controller-based state management for testability.
Files:
lib/ui/relay_management/relay_management_screen.dart- Main UI screenlib/ui/relay_management/relay_management_controller.dart- State management controllertest/ui/relay_management/relay_management_screen_test.dart- UI teststest/ui/relay_management/relay_management_controller_test.dart- Controller tests
Key Features: Add/remove relays, connect/disconnect, health monitoring, manual sync trigger, error handling
Usage: Navigate to "Manage Relays" from the main screen after initialization.
User Session Management
Service for managing user sessions, login, logout, and session isolation. Provides per-user data isolation with separate storage paths and cache directories. Clears cached data on logout and integrates with local storage and sync engine.
Files:
lib/data/session/session_service.dart- Main session management servicelib/data/session/models/user.dart- User modeltest/data/session/session_service_test.dart- Unit tests
Key Methods: login(), logout(), switchSession(), getCurrentUserDbPath(), getCurrentUserCacheDir()
Features: Per-user storage isolation, cache clearing on logout, session switching with data preservation, integration with local storage
Usage: Initialize SessionService with LocalStorageService and optional SyncEngine. Call login() to start a session, logout() to end it, and switchSession() to change users.
Firebase Layer
Optional Firebase integration providing cloud sync, storage, authentication, push notifications, and analytics. Fully modular - can be enabled or disabled without affecting offline-first functionality. Integrates with local storage and session management to maintain offline-first behavior.
Files:
lib/data/firebase/firebase_service.dart- Main Firebase servicelib/data/firebase/models/firebase_config.dart- Firebase configuration modeltest/data/firebase/firebase_service_test.dart- Unit tests
Key Methods: initialize(), loginWithEmailPassword(), logout(), syncItemsToFirestore(), syncItemsFromFirestore(), uploadFile(), getFcmToken(), logEvent()
Features: Firestore cloud sync, Firebase Storage for media, Firebase Auth for authentication, Firebase Cloud Messaging for push notifications, Firebase Analytics for analytics, all optional and modular
Usage: Create FirebaseService with FirebaseConfig (disabled by default). Pass to SessionService for automatic sync on login/logout. Initialize Firebase with initialize() before use. All services gracefully handle being disabled.
Note: Firebase requires actual Firebase project setup with google-services.json (Android) and GoogleService-Info.plist (iOS) configuration files. The service handles missing configuration gracefully and maintains offline-first behavior.
Configuration
Configuration uses .env files for sensitive values (API keys, URLs) with fallback defaults in lib/config/config_loader.dart.
Setup .env File
-
Copy
.env.exampleto.envin the project root:cp .env.example .env -
Edit
.envand fill in your actual values:IMMICH_BASE_URL- Your Immich server URLIMMICH_API_KEY_DEV- Your development Immich API keyIMMICH_API_KEY_PROD- Your production Immich API keyNOSTR_RELAYS_DEV- Comma-separated Nostr relay URLs for devNOSTR_RELAYS_PROD- Comma-separated Nostr relay URLs for prod- Other configuration values as needed
Important: The .env file is in .gitignore and should never be committed to version control. Only commit .env.example as a template.
Environment Variables
The app uses:
.envfile (recommended) - Loaded at runtime, falls back to defaults if not found--dart-define- For environment selection:
# Set environment at runtime
flutter run --dart-define=ENV=prod
If .env file is not found or variables are missing, the app uses default values from lib/config/config_loader.dart.
Available Environments
dev- Development (default): Logging enabled, dev API URLprod- Production: Logging disabled, production API URL
Running the App
Android Emulator
Important: Wait for emulator to fully boot (30-60 seconds on cold boot).
- Start emulator from Android Studio AVD Manager
- Wait for boot animation to complete and home screen appears
- Run:
flutter run
iOS Simulator (macOS)
open -a Simulator
flutter run
Running Tests
# Run all tests
flutter test
# Run with coverage
flutter test --coverage
Important: Tests must be run separately - flutter run does not execute tests.
Project Structure
lib/
├── config/
│ ├── app_config.dart
│ └── config_loader.dart
├── data/
│ ├── local/
│ │ ├── local_storage_service.dart
│ │ └── models/
│ │ └── item.dart
│ ├── immich/
│ │ ├── immich_service.dart
│ │ └── models/
│ │ ├── immich_asset.dart
│ │ └── upload_response.dart
│ ├── nostr/
│ │ ├── nostr_service.dart
│ │ └── models/
│ │ ├── nostr_keypair.dart
│ │ ├── nostr_event.dart
│ │ └── nostr_relay.dart
│ ├── session/
│ │ ├── session_service.dart
│ │ └── models/
│ │ └── user.dart
│ ├── firebase/
│ │ ├── firebase_service.dart
│ │ └── models/
│ │ └── firebase_config.dart
│ └── sync/
│ ├── sync_engine.dart
│ └── models/
│ ├── sync_status.dart
│ └── sync_operation.dart
├── ui/
│ └── relay_management/
│ ├── relay_management_screen.dart
│ └── relay_management_controller.dart
└── main.dart
test/
├── config/
│ └── config_loader_test.dart
└── data/
├── local/
│ └── local_storage_service_test.dart
├── immich/
│ └── immich_service_test.dart
├── nostr/
│ └── nostr_service_test.dart
├── session/
│ └── session_service_test.dart
├── firebase/
│ └── firebase_service_test.dart
├── sync/
│ └── sync_engine_test.dart
└── ui/
└── relay_management/
├── relay_management_screen_test.dart
└── relay_management_controller_test.dart