Kotlin and Android Development featuring Jetpack: SQLite database not removed by uninstallation of PennyDrop

This isn’t errata per se, but I discovered something unexpected while working through Chapter 5. I’m not sure whether I just misunderstood how SQLite works, or whether it’s a bug in Arctic Fox (Android Studio 2020.3.1 patch 2), the x86 Emulator, or the Android 11 ROM it’s running, but it appears that uninstalling PennyDrop does NOT blow away the underlying SQLite database.

How to replicate:

  1. Build and run PennyDrop in the emulator using the chapter 5 code from the .zip file

  2. Play a game or two, giving the player a distinctive name you’ll recognize later.

  3. Kill PennyDrop in the emulator by clicking the red ‘stop’ icon in Android Studio.

  4. In the Emulator, go to Settings → Apps, find PennyDrop, and uninstall it.

  5. Go back to Android Studio, and re-launch PennyDrop by clicking the green ‘play’ icon. It’ll re-deploy and start.

  6. Attempt to start a new game, and observe it crash with a SQLiteConstraintException (UNIQUE constraint failed on primary key)

It’s been a few years since I’ve used “raw” SQLite, so I don’t remember offhand whether it defaults to putting your database files in the app’s private data dir, or whether the app is expected to tell it where to put them (and the private data dir is simply an obvious & sensible place)… but it looks like Jetpack Room does NOT put its SQLite data files there, and instead puts them somewhere else that doesn’t automatically get blown away when the app gets uninstalled.

Of course, it’s also possible that Google built a custom ROM for the emulator that intentionally leaves the data dir of uninstalled apps behind for forensics purposes, or that it’s actually a developer setting somewhere that I’ve just overlooked for years. It might also be an outright bug in Arctic Fox, or Room 2.3.0, or all the above and more.

Either way, the only solution I’ve found is to modify PennyDropDatabase.kt as follows:

  1. Increase the ‘version’ number in the @Database annotation:
    @Database(
        entities = [Game::class, Player::class, GameStatus::class],
->      version = 2,
        exportSchema = false
    )
  1. Add a call to fallbackToDestructiveMigration() to the builder:
    val instance = Room.databaseBuilder(
        context,
        PennyDropDatabase::class.java,
        "PennyDropDatabase" )
->      .fallbackToDestructiveMigration()
        .addCallback(object : RoomDatabase.Callback() {
        override fun onCreate(db : SupportSQLiteDatabase) {
        // ...

As I understand it, the call to fallbackToDestructiveMigration() basically tells it, “if the database version changes, blow away the old one and rebuild it from scratch according to the new specs without attempting to migrate anything”.

Given everything I’ve always thought I’ve known about the implied behavior of Android apps when uninstalled, the persistence of Room-created SQLite databases after the creator’s uninstallation is pretty shocking, and almost has to be a bug in Room unless it’s just the side effect of a developer option I overlooked. Otherwise, this would leave the door open to a SERIOUS (flash) memory leak for an Android device… a misbehaving app could create a multi-gigabyte Room database that would persist even after the app that created it were uninstalled, and would (AFAIK) be impossible to remove by hand unless the device were rooted.

Update:

According to this post at StackOverflow (android - Remove room database on app uninstall - Stack Overflow), the android:allowBackup=“true” in AndroidManifest.xml might be the root of the problem. Apparently, SQLite databases are one of the things that automatically get backed up.

From what I’ve read so far, once the backup gets made, deleting it is “a pain”, because the backups are almost viral. Merely changing allowBackups to false will prevent future backups, but won’t prevent the existing backup from getting auto-restored in perpetuity upon reinstallation. It looks like the only official way to purge the backup once it has been created is to make the app uninstallation-aware, and add code to the DAO class to explicitly drop the tables as part of the uninstallation process.