Conversation
There was a problem hiding this comment.
Pull request overview
This PR appears to address crashes/edge-cases referenced by #186 and #161 by improving external link handling in Settings and making stroke persistence more resilient to SQLite uniqueness conflicts in the editor.
Changes:
- Replaced direct
startActivityURL launching with a sharedopenInBrowser(...)helper and added an error snackbar onActivityNotFoundException. - Switched Settings tab titles to
stringResource(...)for Compose-friendly localization lookup. - Wrapped stroke insertion in a try/catch for
SQLiteConstraintExceptionand attempted a fallbackupdate(...)on conflict.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| app/src/main/java/com/ethran/notable/ui/views/Settings.kt | Adds openInBrowser helper and routes Settings external links through it, plus Compose string changes. |
| app/src/main/java/com/ethran/notable/editor/PageView.kt | Handles stroke insert constraint exceptions by falling back to update and moves stroke persistence to an IO coroutine. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private fun saveStrokesToPersistLayer(strokes: List<Stroke>) { | ||
| dbStrokes.create(strokes) | ||
| coroutineScope.launch(Dispatchers.IO) { | ||
| try { | ||
| dbStrokes.create(strokes) |
There was a problem hiding this comment.
saveStrokesToPersistLayer now launches a DB write on the coroutineScope passed into PageView. In EditorView, that scope comes from rememberCoroutineScope() and will be cancelled when the composable leaves composition; this means stroke persistence can be cancelled on navigation/close and strokes may never be written to the DB. Consider using a dedicated persistence scope in PageView (e.g., SupervisorJob()+Dispatchers.IO cancelled explicitly in disposeOldPage()), or otherwise ensure persistence isn't cancelled mid-write.
| try { | ||
| dbStrokes.create(strokes) | ||
| } catch (_: SQLiteConstraintException) { | ||
| // There were some rare bugs when strokes weren't unique when inserting from history | ||
| // I'm not sure if its still a problem, lets just show the message | ||
| logAndShowError( | ||
| "saveStrokesToPersistLayer", | ||
| "Attempted to create strokes that already exist" | ||
| ) | ||
| dbStrokes.update(strokes) |
There was a problem hiding this comment.
Catching SQLiteConstraintException and then calling dbStrokes.update(strokes) can still lose data: Room's @Insert for a list runs in a transaction by default, so if any stroke conflicts, the entire insert may abort and none of the new strokes are inserted. @Update will not insert missing rows, so any genuinely-new strokes in this batch will be dropped. Prefer an actual upsert approach (e.g., @Insert(onConflict = REPLACE/IGNORE) + follow-up update for ignored rows, or Room @Upsert if available).
| try { | |
| dbStrokes.create(strokes) | |
| } catch (_: SQLiteConstraintException) { | |
| // There were some rare bugs when strokes weren't unique when inserting from history | |
| // I'm not sure if its still a problem, lets just show the message | |
| logAndShowError( | |
| "saveStrokesToPersistLayer", | |
| "Attempted to create strokes that already exist" | |
| ) | |
| dbStrokes.update(strokes) | |
| strokes.forEach { stroke -> | |
| try { | |
| // Try to create each stroke individually so a conflict on one | |
| // stroke does not prevent others from being inserted. | |
| dbStrokes.create(listOf(stroke)) | |
| } catch (_: SQLiteConstraintException) { | |
| // There were some rare bugs when strokes weren't unique when inserting from history | |
| // I'm not sure if its still a problem, lets just show the message | |
| logAndShowError( | |
| "saveStrokesToPersistLayer", | |
| "Attempted to create strokes that already exist" | |
| ) | |
| // Fall back to updating the existing stroke (upsert-like behavior). | |
| dbStrokes.update(listOf(stroke)) | |
| } |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (!dbDir.exists()) { | ||
| dbDir.mkdirs() | ||
| } | ||
| if (!dbDir.canWrite()) { | ||
| throw IllegalStateException("Database directory is not writable") | ||
| } |
There was a problem hiding this comment.
Throwing IllegalStateException here will crash the app during AppDatabase.getDatabase() when the external Documents directory isn’t writable (common on newer Android versions without the right permissions/scoped-storage access). Instead of crashing, handle mkdirs() failures explicitly and fall back to an app-private directory (e.g. context.filesDir) or surface a recoverable error path so the app can still start.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
fixes for #186 and #160, #161, #162