user13438351
user13438351

Reputation:

Using Room database in multiple fragments

I have HomeActivity activity with bottom navigation bar and 5 fragments in it. I want to use RoomDatabase in all of this fragments. How can I implement it?

HomeActivity code:

public class HomeActivity extends AppCompatActivity {

    TextView tvDailyGoalValue;
    SharedPreferences sharedPreferences;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //layout setting
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        BottomNavigationView navView = findViewById(R.id.nav_view);
        AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
                R.id.navigation_home, R.id.navigation_profile, R.id.navigation_notifications)
                .build();
        NavController navController = Navigation.findNavController(this, R.id.fragment3);
        NavigationUI.setupWithNavController(navView, navController);
       sharedPreferences = getSharedPreferences("MyPrefs", MODE_PRIVATE);
       RoomDB roomDB = Room.databaseBuilder(this, RoomDB.class,"dripDB").build();        
    }    
}

Upvotes: 2

Views: 4375

Answers (2)

Salih Kavaf
Salih Kavaf

Reputation: 1145

I'm not claiming that usage of Android Room is or should be complicated but, definitely not as simple as you think it is; looking to your example. Android developers spend a lot of time trying to learn how to use Android Architecture Components and Dagger 2 to make their applications as good as possible, which is, by the way, the recommend APIs by Google to be used with Android Room. Though, I'm still going to answer your question.

1. Entities
Decide what entities you're going to use in your database. Let's say we have only one entity here. Something like:

@Entity
class User constructor(
    @ColumnInfo(name = "first_name")
    var firstName: String,

    @ColumnInfo(name = "last_name")
    var lastName: String

    @PrimaryKey(autoGenerate = true)
    var id: Int = 0,
)

2. DAO class
Create your DAO class like this:

/**
 * Provides the abstraction API for accessing data.
 */
@Dao
interface AppDao {

    /**
     * Inserts a new [User].
     * @param user The user to insert.
     */
    @Insert
    suspend fun insertUser(user: User)

    /**
     * Updates the specified [User].
     * @param user The user to update.
     */
    @Update
    suspend fun updateUser(user: User)

    /**
     * Deletes the specified [User] from the database.
     * @param user The user to delete.
     */
    @Delete
    suspend fun deleteUser(user: User)
}

3. Database class
Create your database in a separate class. (eg. AppDatabase.java) Here is how a good database class looks:

@Database(entities = [User::class], version = AppDatabase.VERSION)
abstract class AppDatabase : RoomDatabase() {

    /**
     * Returns the DAO for this application.
     * @return The DAO for this application.
     */
    abstract fun getAppDao(): AppDao

    companion object {

        private const val TAG = "AppDatabase"

        const val VERSION = 1
        private const val DATABASE_NAME = "inventory_database.db"

        @Volatile
        private var instance: AppDatabase? = null

        /**
         * Gets and returns the database instance if exists; otherwise, builds a new database.
         * @param context The context to access the application context.
         * @return The database instance.
         */
        fun getInstance(context: Context): AppDatabase =
            instance ?: synchronized(this) {
                instance ?: buildDatabase(context).also { instance = it }
            }

        /**
         * Creates and returns the callback object to execute when the database is first created.
         * @return The callback object to execute when the database is first created.
         */
        private fun appDatabaseCallback(): Callback = object : Callback() {

            override fun onCreate(db: SupportSQLiteDatabase) {
                super.onCreate(db)
                Log.d(TAG, "Database has been created.")

                // Throws exception
                CoroutineScope(Dispatchers.IO).launch {
                    instance?.getAppDao()?.let { populateDbAsync(it) }
                }
            }

            override fun onOpen(db: SupportSQLiteDatabase) {
                super.onOpen(db)
                Log.d(TAG, "Database has been opened.")
            }
        }

        /**
         * Builds and returns the database.
         * @param appContext The application context to reference.
         * @return The built database.
         */
        private fun buildDatabase(appContext: Context): AppDatabase {
            val filesDir = appContext.getExternalFilesDir(null)
            val dataDir = File(filesDir, "data")
            if (!dataDir.exists())
                dataDir.mkdir()

            val builder =
                Room.databaseBuilder(
                    appContext,
                    AppDatabase::class.java,
                    File(dataDir, DATABASE_NAME).toString()
                ).fallbackToDestructiveMigration()

            // Execute the callback only in DEBUG mode.
            if (BuildConfig.DEBUG) {
                builder.addCallback(appDatabaseCallback())
            }
            return builder.build()
        }

        /**
         * Populates the database when it is first created, as a suspended operation.
         * @param appDao The application DAO to execute queries.
         */
        private suspend fun populateDbAsync(appDao: AppDao) {

            withContext(Dispatchers.IO) {
                // Populate your database here...
            }

        }
    }
}

4. Activities & Fragments

  • Activity
class MainActivity : AppCompatActivity() {

    private lateinit var dao: AppDao

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Initialize the DAO..
        dao = AppDatabase.getInstance(requireContext()).getAppDao()
    }

    // An example of how to execute a dao method in an activity (Again, not recommended)
    private fun insertUser(firstName: String, lastName: String) {
        val user = User(firstName, lastName)
        
        lifecycleScope.launch { 
            dao.insertUser(user)
        }
    }

    // The rest of the code here...
}

You can do the same for all your activities.

  • Fragment
class ExampleFragment : Fragment(R.layout.fragment_example) {

    private lateinit var dao: AppDao

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Initialize the DAO..
        dao = AppDatabase.getInstance(requireContext()).getAppDao()
    }

    // An example of how to execute a dao method in a fragment (Again, not recommended)
    private fun insertUser(firstName: String, lastName: String) {
        val user = User(firstName, lastName)
        
        lifecycleScope.launch { 
            dao.insertUser(user)
        }
    }

    // The rest of the code here...
}

You can do the same for all your fragments.

Upvotes: 4

npace
npace

Reputation: 4258

You should make a single, globally accessible instance of your RoomDB object. The Room docs even say so:

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.

There are multiple ways you can share a single object across multiple Activities and Fragments, for example:

  1. Use Dagger and configure an @Singleton that you can @Inject in all the relevant Activities and Fragments. Setting up Dagger is somewhat complicated if you're doing it for the first time, but this is the usual choice in professional apps.
  2. Use a classic Java Singleton design pattern and convert RoomDB to a Singleton.
  3. Use a custom Application subclass, put the RoomDB instance there and expose it to any Context, e.g. ((YourCustomApplication)context.getApplication()).getRoomDB()

I'd say your best bet is 1) for scalability or 2) for simplicity.

Upvotes: 0

Related Questions