Skip to content

[AI][logs] Crash: SQLiteConstraintException (UNIQUE constraint failed: Stroke.id) #161

@Ethran

Description

@Ethran

Error log

android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: Stroke.id (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY)
at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java:-2)
at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:709)
at android.database.sqlite.SQLiteSession.execute(SQLiteSession.java:621)
at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:47)
at androidx.sqlite.db.framework.FrameworkSQLiteStatement.execute(FrameworkSQLiteStatement.android.kt:30)
at androidx.sqlite.driver.SupportSQLiteStatement$OtherSQLiteStatement.step(SupportSQLiteStatement.android.kt:588)
at androidx.room.EntityInsertAdapter.insert(EntityInsertAdapter.kt:91)
at com.ethran.notable.data.db.StrokeDao_Impl.create$lambda$1(StrokeDao_Impl.kt:120)
at com.ethran.notable.data.db.StrokeDao_Impl.$r8$lambda$F6YajNQPaF1WozUveMfpQA__9JU(:0)
at com.ethran.notable.data.db.StrokeDao_Impl$$ExternalSyntheticLambda3.invoke(D8$$SyntheticClass:0)
at androidx.room.util.DBUtil__DBUtil_androidKt$performBlocking$1$1$invokeSuspend$$inlined$internalPerform$1$1.invokeSuspend(DBUtil.kt:61)
[...]
at androidx.room.util.DBUtil__DBUtil_androidKt.performBlocking(DBUtil.android.kt:71)
at androidx.room.util.DBUtil.performBlocking(:1)
at com.ethran.notable.data.db.StrokeDao_Impl.create(StrokeDao_Impl.kt:118)
at com.ethran.notable.editor.PageView.saveStrokesToPersistLayer(PageView.kt:315)
at com.ethran.notable.editor.PageView.addStrokes(PageView.kt:295)
at com.ethran.notable.editor.state.History.treatOperation(history.kt:103)
at com.ethran.notable.editor.state.History.undoRedo(history.kt:146)
at com.ethran.notable.editor.state.History.access$undoRedo(history.kt:40)
at com.ethran.notable.editor.state.History$1$1.emit(history.kt:70)
at com.ethran.notable.editor.state.History$1$1.emit(history.kt:61)
at kotlinx.coroutines.flow.SharedFlowImpl.collect$suspendImpl(SharedFlow.kt:392)
at kotlinx.coroutines.flow.SharedFlowImpl$collect$1.invokeSuspend(:14)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
[...]

Possible cause
The log shows a UNIQUE constraint violation on the primary key of the Stroke table (Stroke.id). The code attempted to insert a stroke record with an id value that already exists in the database. This can happen if the code tries to insert a duplicate stroke, or if the mechanism to generate unique stroke IDs failed or produced a collision.

No confirmed reproduction case is known. Only the error log and code context are available.

Referenced code:

Stroke entity (primary key definition):

@kotlinx.serialization.Serializable
data class StrokePoint(
val x: Float, // with scroll
var y: Float, // with scroll
val pressure: Float? = null, // relative pressure values 1 to 4096, usually whole number
val tiltX: Int? = null, // tilt values in degrees, -90 to 90
val tiltY: Int? = null,
val dt: UShort? = null, // delta time in milliseconds, from first point in stroke, not used yet.
@SerialName("timestamp") private val legacyTimestamp: Long? = null,
@SerialName("size") private val legacySize: Float? = null,
)
@Entity(
foreignKeys = [ForeignKey(
entity = Page::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("pageId"),
onDelete = ForeignKey.CASCADE
)]
)
data class Stroke(
@PrimaryKey
val id: String = UUID.randomUUID().toString(),
val size: Float,
val pen: Pen,
@ColumnInfo(defaultValue = "0xFF000000")
val color: Int = 0xFF000000.toInt(),
@ColumnInfo(defaultValue = "4096")
val maxPressure: Int = 4096, // might be useful for synchronization between devices
var top: Float,
var bottom: Float,
var left: Float,
var right: Float,
val points: List<StrokePoint>,
@ColumnInfo(index = true)
val pageId: String,
val createdAt: Date = Date(),
val updatedAt: Date = Date()
)
// DAO
@Dao
interface StrokeDao {
@Insert
fun create(stroke: Stroke): Long
@Insert
fun create(strokes: List<Stroke>)
@Update
fun update(stroke: Stroke)
@Query("DELETE FROM stroke WHERE id IN (:ids)")
fun deleteAll(ids: List<String>)
@Transaction
@Query("SELECT * FROM stroke WHERE id =:strokeId")
fun getById(strokeId: String): Stroke
}
class StrokeRepository(context: Context) {
var db = AppDatabase.getDatabase(context).strokeDao()
fun create(stroke: Stroke): Long {
return db.create(stroke)
}
fun create(strokes: List<Stroke>) {
return db.create(strokes)
}
fun update(stroke: Stroke) {
return db.update(stroke)
}
fun deleteAll(ids: List<String>) {
return db.deleteAll(ids)
}
fun getStrokeWithPointsById(strokeId: String): Stroke {
return db.getById(strokeId)
}
}

private fun saveStrokesToPersistLayer(strokes: List<Stroke>) {
dbStrokes.create(strokes)
}

  • PageView methods (call sites in the stack trace):

  • History functions referenced in the stack trace:

    • treatOperation (invokes pageModel.addStrokes / removeStrokes):
      duration = 3000,
      )
      )
      }
      }
      is HistoryBusActions.RegisterHistoryOperationBlock -> {
      addOperationsToHistory(actions.operationBlock)
      }
      }
      }
      fun cleanHistory() {
      undoList.clear()
      redoList.clear()
      }
      private fun treatOperation(operation: Operation): Pair<Operation, Rect> {
      when (operation) {
      is Operation.AddStroke -> {
      pageModel.addStrokes(operation.strokes)
      return Operation.DeleteStroke(strokeIds = operation.strokes.map { it.id }) to strokeBounds(
      operation.strokes
      )
      }
      is Operation.DeleteStroke -> {
      val strokes = pageModel.getStrokes(operation.strokeIds).filterNotNull()
      pageModel.removeStrokes(operation.strokeIds)
      return Operation.AddStroke(strokes = strokes) to strokeBounds(strokes)
      }
      is Operation.AddImage -> {
      pageModel.addImage(operation.images)
      return Operation.DeleteImage(imageIds = operation.images.map { it.id }) to imageBoundsInt(
      operation.images
      )
      }
      is Operation.DeleteImage -> {
      val images = pageModel.getImages(operation.imageIds).filterNotNull()
      pageModel.removeImages(operation.imageIds)
      return Operation.AddImage(images = images) to imageBoundsInt(images)
      }
      }
      }
      private fun undoRedo(type: UndoRedoType): Rect? {
      val originList =
    • undoRedo (moves across history and calls treatOperation internally):
      https://github.com/Ethran/notable/blob/e6c2af53eee3399ae4b203f2baa8547982fb8019/app/src/main/java/com/ethran/notable/editor/state/history.kt#L110-L200

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions