LoginSignup
2
0

エラーに直面した:android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY[787])

Posted at

表題のエラーに直面して解決まで辿り着いたので共有します。

環境

Android Studio Hedgehog | 2023.1.1 Patch 2

経緯

Androidアプリの開発の中で、Roomを実装中にこのエラーに直面しました。

クラッシュした箇所


suspend fun operate() {
        getCityCodeJsonDataSource.fetchResponseAsFlow().collect { list ->
list.forEachIndexed { i, data ->

                val prefecture = Prefecture(
                    i,
                    data.prefectureCode!!,
                    data.prefectureName!!
                )

                //logをはくメソッド
                footprint("prefecture: $prefecture")
                MainApplication().appDatabase.prefectureAndCityDao().upsertPrefecture(prefecture)

                val cities = data.municipals
                cities?.forEachIndexed { j, city ->
                    val c = City(
                        j,
                        city.code,
                        city.name,
                        city.kana,
                        city.isDisabled,
                        i
                    )

                    //logをはくメソッド
                    footprint("city: $c")
                    //***ここでクラッシュ(android.database.sqlite.SQLiteConstraintException...)***
                    MainApplication().appDatabase.prefectureAndCityDao().upsertCity(c)
                }

            }
    

    }

原因

端的にいうと「『親』の準備が完了する前に『子』のデータを入れるな!」ということのようです。
実は下記のように2つのモデルは親子関係にあります。


import androidx.room.ColumnInfo
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import androidx.room.Relation

@Entity(tableName = "prefecture")
data class Prefecture(
    @PrimaryKey(autoGenerate = true)
    val id: Int,
    @ColumnInfo(name = "prefecture_name")
    val prefectureName: String,
    @ColumnInfo(name = "prefecture_code")
    val prefectureCode: String,
)

@Entity(
    tableName = "city",
    foreignKeys = [ForeignKey(
        entity = Prefecture::class,
        parentColumns = arrayOf("id"),
        childColumns = arrayOf("p_id"),
        onDelete = ForeignKey.CASCADE
    )]
)
data class City(
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "c_id")
    val cId: Int,
    val code: String?,
    val name: String?,
    @ColumnInfo(name = "name_kana")
    val nameKana: String?,
    @ColumnInfo(name = "prefecture_code")
    val isDisabled: Boolean?,
    @ColumnInfo(name = "p_id")
    val pId: Int
)

data class PrefectureAndCity(
    @Embedded
    val prefecture: Prefecture,
    @Relation(
        parentColumn = "id",
        entityColumn = "p_id"
    )
    val cities: List<City>
)

解法

以上のことを踏まえると、データを入れる順番に問題があったことになるので、下記のように親(Prefecture)のデータを全て入れ終わった後に子(City)を入れてあげると上手くいきます。


suspend fun operate() {
        getCityCodeJsonDataSource.fetchResponseAsFlow().collect { list ->

            list.forEachIndexed { i, data ->

                val prefecture = Prefecture(
                    i + 1, data.prefectureCode!!, data.prefectureName!!
                )

                footprint("prefecture: $prefecture")
                MainApplication().appDatabase.prefectureAndCityDao().upsertPrefecture(prefecture)

            }

            list.forEachIndexed { i, data ->

                val cities = data.municipals
                cities?.forEachIndexed { j, city ->
                    val c = City(
                        j + 1,
                        city.code,
                        city.name,
                        city.kana,
                        city.isDisabled,
                        i + 1
                    )
                    footprint("city: $c")
                    MainApplication().appDatabase.prefectureAndCityDao().upsertCity(c)
                }

            }

        }
    }

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0