Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I'm working on an app that uses Room database; it's a static database that has 3 tables that I insert their data at the first time the app launches by overriding onCreate() upon database instantiation.

It takes a while until inserting all the static records to the three tables, so my app can't access database until the onCreate() is over to get the right data back to be shown to the user.

When I try to access the database within the activity's onCreate() method, it returns null data, and this literally interrupts the Room's onCreate operation, which lead to null data for upcoming launches of the app as the Room's onCreate() is called only once.

For debugging purpose, when I get rid of this database access only for the first time launch of the app (after clean uninstall) to allow the database to build-up for the first launch; then when I re-add the database query to my activity's onCreate() and relaunch the app; it returns non-null data; I just wanted to make sure that Room's onCreate() is over of the 1st launch.

I can pass a listener to the custom Room RoomDatabase class where its callback can be triggered upon the end of onCreate() method; but I'm not sure if this is the right way to do this or there is another elegant way, and if this is the right way; then how to implement it the right way taking the MVVM design pattern, stuff like Repository , ViewModel into consideration.

Here's how the database class looks like

@Database(entities = {Table1.class, Table2.class, Table3.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
    public static final String DATABASE_NAME = "database.db";
    private static volatile AppDatabase INSTANCE;
    private static final Object LOCK = new Object();
    public abstract Table1Dao getTable1Dao();
    public abstract Table2Dao getTable2Dao();
    public abstract Table2Dao getTable3Dao();
    private static Executor mExecutor = Executors.newSingleThreadExecutor();
    static public AppDatabase getInstance(final Context context) {
        if (INSTANCE == null) {
            synchronized (LOCK) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                            AppDatabase.class, DATABASE_NAME)
                            .addCallback(new Callback() {
                                @Override
                                public void onCreate(@NonNull SupportSQLiteDatabase db) {
                                    mExecutor.execute(() -> table1InsertOperation(context));
                                    mExecutor.execute(() -> table2InsertOperation(context));
                                    mExecutor.execute(() -> table3InsertOperation(context));
                                   super.onCreate(db);
                            .build();
        return INSTANCE;

Do appreciate your help

"When I try to access the database within the activity's onCreate() method, it returns null data, and this literally interrupts the Room's onCreate operation, which lead to null data for upcoming launches of the app as the Room's onCreate() is called only once." This seems pretty weird to me, a db is made to allow multiple threads to read and write, so why that should happen?

"I can pass a listener to the custom Room RoomDatabase class where its callback can be triggered upon the end of onCreate() method" I see you use an Executor to run the population phase on background threads, so onCreate should finish almost immediately, the threads that populate the db most probably much later. You would have to monitor when all the three threads you run all finish their work. Same as the listener, but much easier (once you solve point 1 that seems a bug somewhere in your code or less probably in Room) would be just to have a LiveData (or Flow) that tells you when all the rows are inserted.

Look at this repo: https://gist.github.com/florina-muntenescu/697e543652b03d3d2a06703f5d6b44b5 she uses an Executors.newSingleThreadExecutor() and seems similar to what you do

Ps. Android Studio 4.1 has an amazing Database Inspector, try it!

Hi Andrew .. you're right, but the problem that I try to access the database with a query while it's still building the initial data with its CallBack ... this access interrupts the operation of this callback (which is inserting the intial data in the 3 tables) ... so when I relaunch the app again it finds null data on Room database as Room's OnCreate can only be called once and only once as documentation says. Thanks for your answer and for the shared repo – Zain Jun 17, 2020 at 21:42 Hi @Zain, an access (read or write) to a database doesn't interrupt any other operation on the same database. You can spawn one million threads and do all the sort of operations (select, insert, update and deletes) in those. the database will stay always consistent. Yes, you could have for example an error if in one thread you insert a row with the same id of a row that was already inserted from another thread, and that error could stop all the subsequent operations of a transaction. but you need to post what you do on the populate functions and on the other access to see if that's the case. – AndrewBloom Jun 17, 2020 at 21:54 you said "it's a static database that has 3 tables that I insert their data at the first time" so i thought you would do all the insert on your populate phase and only select on the access, which should not give problems. By the way, why you use 3 threads on the onCreate? could be that the randomisation of order of operations given by the 3 threads causes randomly the problem? I'd go with one thread there (the db most probably parallelise the operations with his thread pool anyway) – AndrewBloom Jun 17, 2020 at 22:03 It's normal that you could get different data based on when the threads execute their operations, to make thinks easier for the developer, Room can return a LiveData, so you can observe on that LiveData and your UI will be updated when the data changes (in your case when the insert finishes, if the select was run before). – AndrewBloom Jun 17, 2020 at 22:15

Use a development version of your app or other tool (e.g. sqlite3) to create a database file containing your fixed data. Place the DB file in your app's assets/ directory and use it to prepopulate your database.

See the docs for createFromAsset() for details.

The think the resulting app database will be ready for use immediately after returning from RoomDatabase.build(). You could also use the onOpen() callback.

Thanks so much Bob .. I will be trying to do that, hopefully it won't affect the app size – Zain Jun 17, 2020 at 21:44

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.