Skip to content

Comments

feat: Add the possibility to export and import the database#2

Merged
M0nkeySan merged 8 commits intomainfrom
feature/database-export-import
Jan 31, 2026
Merged

feat: Add the possibility to export and import the database#2
M0nkeySan merged 8 commits intomainfrom
feature/database-export-import

Conversation

@M0nkeySan
Copy link
Owner

No description provided.

- Add DatabaseExporter interface and implementation
- Create platform-specific FileSaver for Android, iOS, and WASM
- Add export/import UI in Settings screen with warning dialog
- Support JSON format for human-readable backups
- Add string resources in English and French
- Add Upload/Download icons to GameIcons
- Add SQL queries for bulk delete operations
- Implement destructive import with user warning
- Create FeedbackMessage data class with FeedbackType enum (SUCCESS/ERROR)
- Update SettingsViewModel to emit typed feedback messages using string resource keys
- Add SnackbarHostState and LaunchedEffect to SettingsScreen
- Display success/error snackbars with proper localization
- Use app's existing snackbar infrastructure (AppSnackbarHost)
- Success messages: green, auto-dismiss after 3 seconds
- Error messages: red with dismiss button, auto-dismiss after 5 seconds
- Resolve string resources in composable context for proper localization
- Wrap snackbar display in scope.launch to ensure proper coroutine context
- Add 100ms delay before clearing feedback message
- Move clearFeedbackMessage inside scope.launch to prevent premature clearing
- Add missing imports: kotlinx.coroutines.launch and kotlinx.coroutines.delay

This fixes the issue where snackbars weren't appearing on WASM due to:
1. Browser modal dialogs blocking UI thread during file operations
2. Synchronous file download triggering immediate success response
3. Feedback message being cleared before snackbar could render

The fix follows the same pattern used in HomeNavigationTemplate and ensures
the snackbar has time to render before the feedback state is cleared.
- Remove unnecessary scope.launch wrapper inside LaunchedEffect
- LaunchedEffect already provides coroutine context for suspend functions
- Follows the pattern used in YahtzeeScoringScreen and TarotScoringScreen
- Fixes snackbars not appearing on WASM platform
- Keeps 100ms delay for safety (harmless, may help with timing)

Previous commit (384aade) incorrectly added nested scope thinking it was
a timing issue, but this actually caused the bug by creating nested
coroutine contexts that WASM's event loop doesn't handle well.
- Create rememberFileSaver() Composable for all platforms
- Android: Get Activity from LocalContext.current
- iOS/WASM: Return singleton FileSaver in remember block
- Remove FileSaver field from SettingsViewModel
- Add FileSaver parameter to exportDatabase() and importDatabase()
- Update SettingsScreen to call rememberFileSaver() and pass to ViewModel

This fixes the Android crash:
  UnsupportedOperationException: getFileSaver() cannot be called
  directly on Android. Use getFileSaver(activity: AppCompatActivity) instead.

Root cause: SettingsViewModel tried to initialize FileSaver at
construction time, but Android FileSaver requires Activity context
which is only available during composition.

Solution: Follow the pattern used by ShakeDetector and HapticFeedback
- Move platform dependency to UI layer (Composable)
- ViewModel receives FileSaver as function parameter (dependency injection)
- Clean architecture: ViewModel stays platform-agnostic and testable

Related files:
- FileSaver.kt:32 - Added rememberFileSaver() expect declaration
- FileSaver.android.kt:109-140 - Android implementation with LocalContext
- FileSaver.ios.kt:131-134 - iOS implementation
- FileSaver.wasmJs.kt:40-43 - WASM implementation
- SettingsViewModel.kt:62,102 - Added fileSaver parameter
- SettingsScreen.kt:76,163,220 - Use rememberFileSaver()
…rash

- Replace activity.registerForActivityResult() with rememberLauncherForActivityResult()
- Move FileSaver implementation from class to anonymous object in Composable
- Use Channels for coordinating between launcher callbacks and suspend functions
- Keep AndroidFileSaver class for backwards compatibility

This fixes the Android crash:
  IllegalStateException: LifecycleOwner MainActivity is attempting to
  register while current state is RESUMED. LifecycleOwners must call
  register before they are STARTED.

Root cause: activity.registerForActivityResult() must be called BEFORE
Activity reaches STARTED state, but we were calling it during composition
when Activity was already RESUMED.

Solution: rememberLauncherForActivityResult() is the Compose-native way
to register activity result launchers. Compose runtime handles lifecycle
timing automatically, ensuring registration happens at the correct moment.

Technical changes:
- rememberFileSaver() now creates anonymous FileSaver object
- Uses rememberLauncherForActivityResult() for both CreateDocument and OpenDocument
- Channels (createDocumentChannel, openDocumentChannel) coordinate async results
- Same external API (saveJsonFile, pickJsonFile) - no breaking changes
- Context from LocalContext.current used for ContentResolver operations

Benefits:
- Lifecycle-safe: No more registration timing issues
- Compose best practices: Uses official Compose Activity integration
- Simpler: No need to find Activity from Context hierarchy
- Testable: Logic is self-contained in Composable

Files changed:
- FileSaver.android.kt:118-213 - Refactored rememberFileSaver()
- Removed findActivity() extension (no longer needed)
- Added import: rememberLauncherForActivityResult, Uri
@M0nkeySan M0nkeySan merged commit cd202d7 into main Jan 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant