5

I am trying to migrate a project to Android Room. After reading the Android Room documentation I noticed that a Singleton is suitable for accessing my database.

Quote from Android Developer:

Note: If your app runs in a single process, you should follow the singleton design pattern when instantiating an AppDatabase object. Each RoomDatabase instance is fairly expensive, and you rarely need access to multiple instances within a single process.

I wrote the following piece of code:

@Database(entities = {Category.class, News.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

    private static final String DB_NAME = "database.db";
    private static AppDatabase instance;

    public abstract CategoryDao categoryDao();
    public abstract NewsDao newsDao();

    private AppDatabase () {}

    public static AppDatabase getInstance(Context context) {
        if (instance == null) {
            synchronized (AppDatabase.class) {
                if (instance == null) {
                    instance = Room.databaseBuilder(context.getApplicationContext(),
                            AppDatabase.class, DB_NAME).build();
                }
            }
        }
        return instance;
    }
}

Just a simple double checking locking Singleton.

I have read some guides/tutorials and almost everyone comes with a similar approach, however I can see some problems with this approach:

  • Need to pass a Context every single time, even only you need it one time to initialize the Singleton.
  • What if I need to access the database without a Context available?
  • It's even admissible to send parameters to a Singleton?

Any ideas how to implement a Room Database Singleton that solves these problems?

I would like to avoid DI libraries like Dagger2 if possible.

Exprove
  • 1,301
  • 2
  • 18
  • 32
  • Where that "Quote from Android Developer:" comes from? Please be specific, add name, surname, rank and link to the quote source. – Marian Paździoch Apr 13 '20 at 07:37
  • And I think your "double checking locking Singleton" is wrong, see "One thing to keep in mind with this pattern is that the field needs to be volatile to prevent cache incoherence issues." -> https://www.baeldung.com/java-singleton-double-checked-locking – Marian Paździoch Apr 13 '20 at 07:41

5 Answers5

3

You can initialize your database and save an instance of it in your Application class something like this.

public class MyApplication extends Application {

    public AppDatabase database;

    @Override
    public void onCreate() {
        super.onCreate();

        database = AppDatabase.getInstance(this)
    }
}

Later, you can access your reference it using:

((MyApplication)activity).database

Hope this would help.

Gourav
  • 2,746
  • 5
  • 28
  • 45
ArbenMaloku
  • 548
  • 4
  • 15
0
@Database(entities = {Category.class, News.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

   private static final String DB_NAME = "database.db";
   private static AppDatabase instance;
   public abstract CategoryDao categoryDao();
   public abstract NewsDao newsDao();

   private AppDatabase () {}

   // Use this to call on any place
   public static AppDatabase getInstance() {
      return instance;
   }
   // Use once to Create and setup the object
   public static AppDatabase setInstance(Context context) {
      if (instance == null) {
         synchronized (AppDatabase.class) {
            if (instance == null) {
                instance = Room.databaseBuilder(context.getApplicationContext(),
                        AppDatabase.class, DB_NAME).build();
            }
        }
     }
     return instance;
   }
}

// Second you need to set instance on Application Class which create and make your DB 
  //Ready for Use Before anything perform
public class MyApplication extends Application {
   @Override
   public void onCreate() {
      super.onCreate();
      AppDatabase.setInstance(this)
   }
}

To Use

//Need to call
AppDatabase.getInstance().categoryDao();
TBG
  • 154
  • 4
  • 18
Jatin Sahgal
  • 119
  • 6
0

You can instantiate the db then lock the instance.

Also, calling context.applicationContext return the context of the single, global Application object of the current process. This Context's lifecycle is separate from the current context, that is tied to the lifetime of the process rather than the current component.

/**
 * The Room Database that contains the item table.
 */
@Database(entities = arrayOf(Item::class), version = 1, exportSchema = false)
abstract class ItemDb : RoomDatabase() {
    abstract fun itemDao(): ItemDao

    companion object {
        private var INSTANCE: ItemDb? = null

        private val lock = Any()

        @JvmStatic
        fun getInstance(context: Context): ItemDb {

            // When calling this instance concurrently from multiple threads we're in serious trouble:
            // So we use this method, 'synchronized' to lock the instance
            synchronized(lock) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.applicationContext, ItemDb::class.java, "items.db").build()
                }
                return INSTANCE!!
            }
        }
    }
}
0

There is a lot of things to consider here. It totally depends on your use case - because you might have your app to touch the db only on single thread so you do not need any synchronization there at all.

However if you want some complementary solution (might also call it "overkill" in situations I mentioned above), please check https://github.com/android/architecture-components-samples which I paste here for reference only (discussion about this approach is here:

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.observability.persistence

import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context

/**
 * The Room database that contains the Users table
 */
@Database(entities = [User::class], version = 1)
abstract class UsersDatabase : RoomDatabase() {

    abstract fun userDao(): UserDao

    companion object {

        @Volatile private var INSTANCE: UsersDatabase? = null

        fun getInstance(context: Context): UsersDatabase =
                INSTANCE ?: synchronized(this) {
                    INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
                }

        private fun buildDatabase(context: Context) =
                Room.databaseBuilder(context.applicationContext,
                        UsersDatabase::class.java, "Sample.db")
                        .build()
    }
}

In general - you might want to take a look at Kotlin double checking singleton and/or Java Double-Checked Locking with Singleton.

Marian Paździoch
  • 8,813
  • 10
  • 58
  • 103
0

You could use this function

public class DatabaseClient {

private Context mCtx;
private static DatabaseClient mInstance;

//our app database object
private final AppDatabase appDatabase;
private DatabaseClient(Context mCtx) {
    this.mCtx = mCtx;

    //creating the app database with Room database builder
    //alldata is the name of the database
    appDatabase = Room.databaseBuilder(mCtx, AppDatabase.class, "alldata").build();
}

public static synchronized DatabaseClient getInstance(Context mCtx) {
    if (mInstance == null) {
        mInstance = new DatabaseClient(mCtx);
    }
    return mInstance;
}

public AppDatabase getAppDatabase() { return appDatabase; }

}

And use this to call getInstance()

DatabaseClient.getInstance(MainActivity.this).getAppDatabase().memberDao();

AppDatabase class look like this:

@Database(entities = {Member.class} ,  version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
public abstract MemberDao memberDao();
}