Reputation:
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
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
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.
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
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:
@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.RoomDB
to a Singleton.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