Android Architecture Components: Room の Migration で IllegalStateException

データを移行せずに捨ててしまうのなら、以下でいいのですが。


Room.databaseBuilder(context, RepoDatabase.class, DB_NAME)
    .fallbackToDestructiveMigration()
    .build();

きっと捨てることができませんよね。

テーブル定義を変更しながら、データを移行しますよね。


Room.databaseBuilder(context, RepoDatabase.class, DB_NAME)
    .addMigrations(FROM_1_TO_2)
    .build();

static final Migration FROM_1_TO_2 = new Migration(1, 2) {
    @Override
    public void migrate(final SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE Repo
                         ADD COLUMN createdAt TEXT");
        }
    };

database.execSQL()でSQLをベタに実行しながらデータを別テーブルにRENAME後、CREATE→INSERT→DROP というかんじでスキーマを変更していますが。

すると、こんなのに遭遇します。

java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you’ve changed schema but forgot to update the version number. You can simply fix this by increasing the version number.

データベースのバージョンナンバーは上げることは、まあ上げるとして、それでもインデクスなどうまく意図通りに移行できてない場合があります。

データモデルにアノテーションで記述した「Room が利用しようとしているスキーマ」と、Migration部分にベタ記述した「SQLiteのスキーマ」が合致しないといけません。

また、最近のAndroidでは、.dbファイルが、OS上で取り回しづらく、実態を把握しづらかったりします。

Roomが認識しようとしているテーブルスキーマは以下で書き出すことができます。


android {
    javaCompileOptions {
        annotationProcessorOptions {
            arguments = ["room.schemaLocation":
                         "$projectDir/schemas".toString()]
            }
        }
    }
}


"tableName": "Repo",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, PRIMARY KEY(`id`))"

モデルクラスに記述したアノテーションのRoomが認識している状態(expected)をSQLで書き出してくれます。

これと、MIGRATION部分のベタSQL(found)を比較すると、意味が分かってきます。

複数フィールドに対してのUNIQUE なインデクスなど、公式ドキュメントとは違う内部的絶賛更新中な処理な部分など、書き出してみると先に進むことができます。


Android Studio のビルドの体感速度を上げる IDEAプラグイン「Nyan Progress Bar」

プログレスバーのカスタマイズプラグインです。

こういうことです。

 

蓄積されていくイライラは少しずつでも解消していきたいものですよね。

Nyan Progress Bar :: JetBrains Plugin Repository


Android 8.0+ (Oreo) で ホーム画面にアイコンを

Android 8.0 では、アプリのショートカットが次のように変更されています。

com.android.launcher.action.INSTALL_SHORTCUT ブロードキャストは、プライベートで暗黙的なブロードキャストになったため、アプリに影響を与えることはなくなりました。代わりに、ShortcutManager クラスの requestPinShortcut() メソッドを使ってアプリのショートカットを作成する必要があります。

ACTION_CREATE_SHORTCUT インテントによって、ShortcutManager クラスを使用して管理するアプリ ショートカットを作成できるようになりました。このインテントでは、ShortcutManager とやり取りをしない以前のランチャーのショートカットも作成できます。これまで、このインテントでは以前のランチャーのショートカットしか作成できませんでした。

Extension Function で。コピペ用。


inline fun Context.createShortcutHomeScreen() {
  if (ShortcutManagerCompat.isRequestPinShortcutSupported(this)) {
    val shortcutInfo = ShortcutInfoCompat.Builder(this, "abc123")
        .setIntent(
            Intent(this, MainActivity::class.java).apply {
              action = Intent.ACTION_MAIN // for Oreo
            }
        )
        .setShortLabel("テストです")
        .setIcon(IconCompat.createWithResource(this, R.drawable.ic_favorite_black))
        .build()
    ShortcutManagerCompat.requestPinShortcut(this, shortcutInfo, null)
  } else {
    Timber.d("Shortcut is not supported by your launcher")
  }
}