This document provides comprehensive documentation for all services in the Adati codebase, including usage examples and API reference.
- LoggingService
- PreferencesService
- NotificationService
- ReminderService
- ExportService
- ImportService
- AutoBackupService
- DemoDataService
- Log Helper
Comprehensive logging service with file persistence, log aggregation, and crash tracking.
await LoggingService.init();Note: Must be called before any other service initialization in main.dart.
- File Logging: Logs are written to
adati.login the app's documents directory - Crash Logging: Separate crash logs in
adati_crashes.log - Log Aggregation: Similar log messages are aggregated to reduce noise
- Log Rotation: Automatic log file rotation when size limits are reached
- Log Levels: DEBUG, INFO, WARNING, ERROR, SEVERE
init()- Initialize the logging service (must be called first)disableFileLogging()- Disable file logging (useful for tests)severe(String message, {String? component, Object? error, StackTrace? stackTrace})- Log severe errors/crashesgetLogFile()- Get the path to the main log filegetCrashLogFile()- Get the path to the crash log fileexportLogs()- Export all logs as a stringexportCrashLogs()- Export crash logs as a stringclearLogs()- Clear all log filesgetLastCrashSummary()- Get summary of the last crash
// Log an info message
LoggingService.severe(
'Failed to load habits',
component: 'HabitRepository',
error: e,
stackTrace: stackTrace,
);Note: In practice, use the Log helper from log_helper.dart instead of calling LoggingService directly.
Service for managing user preferences and settings using SharedPreferences.
await PreferencesService.init();- Persistent storage of user preferences
- Type-safe getters and setters for all settings
- Automatic logging of preference changes
- Support for various data types (String, int, bool, double)
init()- Initialize the serviceprefs- Get the SharedPreferences instanceresetAllSettings()- Reset all settings to defaultsclear()- Clear all preferences
getThemeMode()/setThemeMode(String mode)- Theme mode (light, dark, system)getThemeColor()/setThemeColor(int color)- Theme color (ARGB32)getCardElevation()/setCardElevation(double elevation)- Card elevationgetCardBorderRadius()/setCardBorderRadius(double radius)- Card border radius
getLanguage()/setLanguage(String language)- App language (en, ar)
getTimelineDays()/setTimelineDays(int days)- Number of days to show in timelinegetFontSizeScale()/setFontSizeScale(String scale)- Font size scalegetShowStreakBorders()/setShowStreakBorders(bool show)- Show streak bordersgetTimelineCompactMode()/setTimelineCompactMode(bool compact)- Timeline compact mode- And many more display-related preferences...
getAutoBackupEnabled()/setAutoBackupEnabled(bool enabled)- Enable/disable auto-backupgetAutoBackupRetentionCount()/setAutoBackupRetentionCount(int count)- Number of backups to keepgetAutoBackupUserDirectory()/setAutoBackupUserDirectory(String? directory)- Custom backup directorygetAutoBackupLastBackup()/setAutoBackupLastBackup(String? iso8601String)- Last backup timestamp
// Get theme mode
final themeMode = PreferencesService.getThemeMode();
// Set theme mode
await PreferencesService.setThemeMode('dark');
// Check if first launch
if (PreferencesService.isFirstLaunch()) {
// Show onboarding
}Service for managing local notifications across all platforms.
await NotificationService.init();
await NotificationService.requestPermissions();- Cross-platform notification support (Android, iOS, Linux, macOS, Windows)
- Scheduled notifications with exact timing
- Notification channels (Android)
- Notification tap handling with navigation
- Permission management
init()- Initialize the notification servicerequestPermissions()- Request notification permissionsshowNotification({required int id, required String title, required String body, String? payload})- Show immediate notificationscheduleNotification({required int id, required DateTime scheduledDate, required String title, required String body, String? payload})- Schedule a notificationcancelNotification(int id)- Cancel a notificationcancelScheduledNotification(int id, {bool silent = false})- Cancel a scheduled notificationcancelAllNotifications()- Cancel all notificationssetRouter(GoRouter router)- Set router for navigation from notification taps
// Show immediate notification
await NotificationService.showNotification(
id: 1,
title: 'Habit Reminder',
body: 'Don\'t forget to complete your habit!',
payload: '/timeline',
);
// Schedule notification
await NotificationService.scheduleNotification(
id: 2,
scheduledDate: DateTime.now().add(Duration(hours: 1)),
title: 'Daily Reminder',
body: 'Time to check your habits',
);Centralized service for managing habit reminders with platform-specific behavior.
ReminderService.init(habitRepository);- Platform-specific reminder scheduling:
- Android: Precise scheduled notifications + WorkManager fallback
- iOS: Precise scheduled notifications + Background Fetch
- Desktop: App-level periodic checks (when app is open)
- Automatic rescheduling of all reminders
- Cancellation of reminders for deleted habits
init(HabitRepository habitRepository)- Initialize with habit repositoryrescheduleAllReminders()- Reschedule all active reminders for all habitscancelRemindersForHabit(int habitId)- Cancel all reminders for a specific habit
// Initialize
ReminderService.init(habitRepository);
// Reschedule all reminders (called automatically on app startup)
await ReminderService.rescheduleAllReminders();
// Cancel reminders for a deleted habit
await ReminderService.cancelRemindersForHabit(habitId);Service for exporting app data in JSON or CSV format.
- Export all data (habits, entries, streaks)
- Export habits only
- Export settings only
- Support for JSON and CSV formats
- Automatic file naming with timestamps
exportToJSON(List<Habit> habits, List<TrackingEntry> entries, List<Streak> streaks)- Export all data as JSONexportToCSV(List<Habit> habits, List<TrackingEntry> entries, List<Streak> streaks)- Export all data as CSVexportHabitsOnly(List<Habit> habits)- Export only habits as JSONexportSettings()- Export settings as JSON
All methods return Future<String?> where the string is the file path, or null if export failed.
// Export all data
final habits = await repository.getAllHabits();
final entries = await repository.getAllEntries();
final streaks = await repository.getAllStreaks();
final filePath = await ExportService.exportToJSON(habits, entries, streaks);
if (filePath != null) {
print('Exported to: $filePath');
}
// Export habits only
final habitsPath = await ExportService.exportHabitsOnly(habits);
// Export settings
final settingsPath = await ExportService.exportSettings();Service for importing app data from JSON or CSV files.
- Import all data (habits, entries, streaks)
- Import habits only
- Import settings only
- Support for JSON and CSV formats
- Progress callbacks
- Error handling and reporting
ImportResult
success- Whether import was successfulhabitsImported/habitsSkipped- Habit import statisticsentriesImported/entriesSkipped- Entry import statisticsstreaksImported/streaksSkipped- Streak import statisticssettingsImported/settingsSkipped- Settings import statisticserrors- List of error messageswarnings- List of warning messageshasIssues- Whether there are any issues
pickImportFile({String? importType})- Pick an import file (returns file path or null)importAllData(HabitRepository repository, String filePath, void Function(String message, double progress)? onProgress)- Import all dataimportHabitsOnly(HabitRepository repository, String filePath, void Function(String message, double progress)? onProgress)- Import habits onlyimportSettings(String filePath, void Function(String message, double progress)? onProgress)- Import settings only
// Pick and import file
final filePath = await ImportService.pickImportFile();
if (filePath != null) {
final result = await ImportService.importAllData(
repository,
filePath,
(message, progress) {
print('$message: ${(progress * 100).toInt()}%');
},
);
if (result.success) {
print('Imported ${result.habitsImported} habits');
} else {
print('Errors: ${result.errors}');
}
}Service for automatic backup creation and management.
AutoBackupService.init(habitRepository);- Automatic daily backups
- Backup retention management
- Backup restoration
- Custom backup directory support
- Backup information retrieval
BackupInfo
path- Backup file pathdate- Backup datesize- Backup file sizehabitsCount- Number of habits in backupentriesCount- Number of entries in backupstreaksCount- Number of streaks in backupversion- App version when backup was created
init(HabitRepository repository)- Initialize with habit repositorycheckAndCreateBackupIfDue()- Check if backup is due and create one if neededcreateBackup()- Create a backup manuallygetBackupDirectory()- Get the backup directorylistBackups()- List all available backupsgetBackupInfo(String backupPath)- Get information about a backuprestoreFromBackup(String backupPath, HabitRepository repository, {void Function(String message, double progress)? onProgress})- Restore from a backupdeleteBackup(String backupPath)- Delete a backup filecleanupOldBackups()- Delete old backups beyond retention count
// Initialize
AutoBackupService.init(habitRepository);
// Check and create backup if due (called automatically on app startup)
await AutoBackupService.checkAndCreateBackupIfDue();
// Create backup manually
final backupPath = await AutoBackupService.createBackup();
// List backups
final backups = await AutoBackupService.listBackups();
for (final backup in backups) {
print('${backup.date}: ${backup.habitsCount} habits');
}
// Restore from backup
final result = await AutoBackupService.restoreFromBackup(
backupPath,
repository,
(message, progress) => print('$message: ${(progress * 100).toInt()}%'),
);Service for managing demo data (used for onboarding and testing).
- Load demo data from JSON configuration
- Check if demo data exists
- Check if a habit is demo data
- Delete demo data
loadDemoData(HabitRepository repository)- Load demo data from assetshasDemoData(HabitRepository repository)- Check if demo data existsisDemoData(int habitId, HabitRepository repository)- Check if a habit is demo datadeleteDemoData(HabitRepository repository)- Delete all demo data
// Load demo data
await DemoDataService.loadDemoData(repository);
// Check if demo data exists
if (await DemoDataService.hasDemoData(repository)) {
// Show delete demo data button
}
// Delete demo data
await DemoDataService.deleteDemoData(repository);Convenience wrapper around LoggingService for easier logging throughout the app.
Instead of calling LoggingService directly, use the Log helper:
import 'package:adati/core/services/log_helper.dart';
// Debug log (only in debug mode)
Log.debug('Debug message', component: 'MyComponent');
// Info log
Log.info('Info message', component: 'MyComponent');
// Warning log
Log.warning('Warning message', component: 'MyComponent');
// Error log
Log.error(
'Error message',
component: 'MyComponent',
error: e,
stackTrace: stackTrace,
);Log.debug(String message, {String? component})- Debug level logLog.info(String message, {String? component})- Info level logLog.warning(String message, {String? component})- Warning level logLog.error(String message, {String? component, Object? error, StackTrace? stackTrace})- Error level log
Note: For severe errors/crashes, use LoggingService.severe() directly.
Services must be initialized in this order in main.dart:
LoggingService.init()- Must be first (needed for all other logging)PreferencesService.init()- Needed for user preferencesNotificationService.init()- For notificationsReminderService.init()- For reminders (requires habit repository)AutoBackupService.init()- For auto-backups (requires habit repository)
See lib/main.dart for the complete initialization sequence.
- Notifications require
SCHEDULE_EXACT_ALARMpermission for precise scheduling - WorkManager is used as a fallback for reminders
- Background Fetch is used for reminder checks
- Notification permissions are requested on first use
- Reminders are checked periodically when the app is in the foreground
- WorkManager is not available on desktop platforms
- Notifications use browser API
- File operations may have limitations
All services handle errors gracefully:
- Errors are logged using
LoggingService - Services continue to function even if non-critical operations fail
- User-friendly error messages are provided where appropriate
To disable file logging in tests:
LoggingService.disableFileLogging();This prevents log files from being created during test execution.