0

I've upgraded my app to feature a Room database. When the user upgrades to my new app version, I need to import data into the Room Database that was stored in other data structures. At first glance it looks like Room already supports this scenario:

    db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "my-db")
            .addCallback(new RoomDatabase.Callback() {

                @Override
                public void onCreate(@NonNull SupportSQLiteDatabase db) {
                    super.onCreate(db);

                    // IMPORT DATA
                }
            })
            .build();

However, when trying to actually import data, things get more complicated. When I try to import data using Entities, Daos and the AppDatabase, I run into an Exception:

java.lang.IllegalStateException: getDatabase called recursively

It doesn't seem to be possible to import data into the database using all the nice Room Entities, Daos and so forth. The onCreate method does however provide access to the underlying SQLite database. Maybe I'm meant to import data there?

The documentation on using the SQLite database directly is rather thin. And it starts with a big red warning not to access the SQLite database directly but to use the Room abstractions instead!

How should I proceed? How is this usually done?

Do the tables I've defined with Entities already exist at this point? Or do I need to create the tables with SQL statements before I can start to import my data?

user1785730
  • 3,150
  • 4
  • 27
  • 50
  • Have you considered using your existing DB to [prepopulate your Room DB](https://developer.android.com/training/data-storage/room/prepopulate) upon app upgrade? – Bob Snyder Aug 06 '20 at 19:02
  • There is no existing database in my app. – user1785730 Aug 06 '20 at 19:04
  • What are the "other data structures" that contain the existing data? – Bob Snyder Aug 06 '20 at 19:13
  • A HashMap written to a FileOutputStream via Context. – user1785730 Aug 06 '20 at 19:17
  • Provide the "import" implementation code you have written rather than pseudo code. As long as you call super and before you interact with the dB then your database schema / tables should have been created – Mark Aug 06 '20 at 20:48
  • @MarkKeen: not sure what you'd like to see. My code to insert data into the database isn't that special: `db.getDao().insert(someEntity);`. Calling that from within `onCreate` throws aforementioned Exception though. – user1785730 Aug 06 '20 at 23:36
  • 1
    See the answers to this question: https://stackoverflow.com/q/50775527/4815718. One option is to do your import using a separate thread. – Bob Snyder Aug 07 '20 at 00:18
  • You really should be interacting with the `SupportSQLiteDatabase db` and not the RoomDatabase in the callback - `RoomDatabase::getDao` would assume that `onCreate` has finished, or lazly create it on first use, but you then call it again in the callback - hence the recursion - your code IS important as this is a implementation issue, not a library problem. – Mark Aug 07 '20 at 00:32
  • Ok, I was guessing at that. Where can I find some documentation for interacting with the `SupportSQLiteDatabase`? Preferably with respect to the scenario of doing so in `onCreate`. – user1785730 Aug 07 '20 at 00:35
  • 1
    As you have a `SupportSQLiteDatabase` to work with it will be standard mechanisms used for sql databases in Android using insert wth `ContentValues`. Reccomend it is performed inside a transaction to ensure all inserts are performed as one. https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteDatabase & https://stackoverflow.com/questions/32274231/android-sqlite-bulk-insert-using-transactions for a "rough" exmple of "bulk" inserting. Threading should not be an issue as you should be calling your dao on a io thread, which all this work will run in on first use – Mark Aug 07 '20 at 00:43

1 Answers1

0

There are basically two ways to import data into a Room database upon creation. The first uses an ugly workaround but then allows you to use Room Entities and all. The second is to work directly with the SQLiteDatabase instance provided.

1. Insert a guard and work with Room abstractions

    final boolean[] doImport = { false };

    db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "my-db")
            .addCallback(new RoomDatabase.Callback() {
                @Override
                public void onCreate(@NonNull SupportSQLiteDatabase db) {
                    super.onCreate(db);
                    doImport[0] = true;
                }
            })
            .build();

    db.userDao().get(17);

    if (doImport[0]) {
        UserEntity user = new UserEntity();
        user.name = "John Doe";
        db.userDao().insert(user);
    }

The doImport boolean serves as a guard to protocol, whether the onCreate callback has been called. It needs to be an array though, because a new value couldn't be assigned to a simple boolean from within onCreate.

Notice also the seemingly senseless line db.userDao().get(17);. It is necessary to access the database in order for the onCreate callback to be called. Otherwise doImport would remain false at this point, regardless of whether or not the database was newly created.

Finally in the last if block the database may be accessed with all the nice abstractions Room provides.

2. Work with the SQLiteDatabase directly

    db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "my-db")
            .addCallback(new RoomDatabase.Callback() {
                @Override
                public void onCreate(@NonNull SupportSQLiteDatabase db) {
                    super.onCreate(db);

                    db.beginTransaction();
                    ContentValues values = new ContentValues();
                    values.put("name", "John Doe");
                    db.insert("UserEntity", SQLiteDatabase.CONFLICT_ABORT, values);
                    db.setTransactionSuccessful();
                    db.endTransaction();
                }
            })
            .build();

A lot less painful then I suspected. You need to work with strings though to identify database tables and fields, which is error prone!

user1785730
  • 3,150
  • 4
  • 27
  • 50