How to use Room Persistence Library with pre-populated database?
This is how I solved it, and how you can ship your application with a pre-populated database (up to Room v. alpha5)
put your SQLite DB
database_name.db
into theassets/databases
foldertake the files from this repo and put them in a package called i.e.
sqlAsset
in your
AppDatabase
class, modify your Room's DB creation code accordingly:Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class,
"database_name.db")
.openHelperFactory(new AssetSQLiteOpenHelperFactory())
.allowMainThreadQueries()
.build();
Note that you have to use "database_name.db"
and not getDatabasePath()
or other methods: it just needs the name of the file.
How to pre-populate the room database with some data under hilt viewModel?
I tried to write it with the previous idea, roughly, and it works successfully, but I don't know if anything is incorrect, so please let me know if it is!
@Database(entities = [Puzzle::class], version = 1, exportSchema = false)
abstract class PuzzleDatabase : RoomDatabase() {
abstract fun getPuzzleDao() : PuzzleDao
}
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Volatile
private var INSTANCE: PuzzleDatabase? = null
private class PuzzleDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
INSTANCE?.let { /*database ->*/
scope.launch {
// you can pre-populate some data here
// database.getPuzzleDao().insert(Puzzle(0, 90000L, 2L, Puzzles.TWO))
}
}
}
}
@Singleton
@Provides
fun puzzleDataBase(
@ApplicationContext app: Context
) : PuzzleDatabase {
return INSTANCE ?: synchronized(this) {
val scope = CoroutineScope(Dispatchers.IO)
val instance = Room.databaseBuilder(
app,
PuzzleDatabase::class.java,
"puzzle_database"
) .addCallback(PuzzleDatabaseCallback(scope))
.build()
.also { INSTANCE = it }
instance
}
}
@Singleton
@Provides
fun getDao(db: PuzzleDatabase) = db.getPuzzleDao()
}
repository.kt
class PuzzleRepository @Inject constructor (
val puzzleDao: PuzzleDao
) {
val all = puzzleDao.getAll()
}
Pre-populating Room database with Hilt without creating an extra instance of the database
Your provideDatabase
method always creates a new instance whenever it is called: Dagger makes it a singleton by only calling that method once. The only way to get the singleton GameDatabase
instance managed by ApplicationComponent
is to request it as a dependency. Since GameDatabase
will need to depend on itself via GameDao
, this is a circular dependency.
To resolve a circular dependency in Dagger, you can depend on a Provider
or Lazy
:
@Singleton
@Provides
fun provideDatabase(@ApplicationContext context: Context, gameDaoProvider: Provider<GameDao>): GameDatabase {
return Room.databaseBuilder(context, GameDatabase::class.java, GameDatabase.GAME_DB_NAME)
.addCallback(
object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
// Initialize the database with the first game
ioThread {
gameDaoProvider.get().createNewGame(Game())
}
}
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
// Ensure there is always one game in the database
// This will capture the case of the app storage
// being cleared
// This uses the existing instance, so the DB won't leak
ioThread {
val gameDao = gameDaoProvider.get()
if (gameDao.gameCount() == 0) {
gameDao.createNewGame(Game())
}
}
}
}
).build()
}
Cannot pre-populate room database when changing schema
After a long time I realised the problem.
I thought I did not need migration as I only wanted to change the pre-populating database, and not migrate anything currently on the device. However the use of destructive migration also deletes your pre-populating data when you add one to the schema number.
So simply add an empty migration and it works
val migration1: Migration = object : Migration(5, 6) { override fun migrate(database: SupportSQLiteDatabase) {}}
Thanks very much to myself for sorting my problem.
Android Room Pre-populated Data not visible first time
Your DAO returns suspend fun getAllUser(): List<User>
, meaning it's a one time thing. So when the app starts the first time, the DB initialization is not complete, and you get an empty list because the DB is empty. Running the app the second time, the initialization is complete so you get the data.
How to fix it:
- Switch
getAllUser()
to return aFlow
:
// annotations omitted
fun getAllUser(): Flow<List<User>>
- Switch
insertUser
to use aList
// annotations omitted
suspend fun insertUser(users: List<User>)
The reason for this change is reducing the number of times the Flow
will emit. Every time the DB changes, the Flow
will emit a new list. By inserting a List<User>
instead of inserting a single User
many times the (on the first run) Flow
will emit twice (an empty list + the full list) compared to number of user times with a single insert.
Another way to solve this issue is to use a transaction + insert a single user.
- I recommend you use viewModelScope inside the
ViewModel
to launch coroutines so it's properly canceled when theViewModel
is destroyed.
Using pre-populated database in Room by copy database file from assert
I solved after spending 6 hours on researching and R & D .
Context is that : - I want to put already existing finaldb.db(which is present inside assests folder) into room database .
Step 1 :
copy this framework files from here link
Step 2 :
You need to migrate , chill i have code :)
@Database(entities = {Status.class}, version = 1,exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
public abstract DataDao StatusDao();
private static AppDatabase INSTANCE;
public static AppDatabase getDatabase(Context context) {
if (INSTANCE == null) {
INSTANCE = createDatabase(context);
}
return (INSTANCE);
}
private static final Migration MIGRATION_2_3 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
Log.d("kkkk","bc");
String SQL_CREATE_TABLE = "CREATE TABLE IF NOT EXISTS 'Status' " +
"( 'id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," +
" 'category' TEXT NOT NULL," +
" 'sub_category' TEXT NOT NULL," +
" 'content' TEXT NOT NULL," +
" 'favourite' INTEGER DEFAULT(0))";
database.execSQL(SQL_CREATE_TABLE);
}
};
private static AppDatabase createDatabase(Context context) {
RoomDatabase.Builder<AppDatabase> builder =
Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class,
context.getString(R.string.dbase_name));
return (builder.openHelperFactory(new AssetSQLiteOpenHelperFactory())
.allowMainThreadQueries()
.addMigrations(MIGRATION_2_3)
.build());
}
}
In MIGRATION_2_3 you have to create table exactly same as current database(which is present in assests folder)
want to learn about migration
Step 3 :
Now table is created successfully in room database !
In case of crash see your logcat , in which its written in understandable form .
Related Topics
Intent Does Not Set the Camera Parameters
Buttons Not Visible on the Application. What's Wrong
How to Import a Native Library (.So File) into Eclipse
Stop Asynctask Doinbackground Method
How to Align Android Toolbar Menu/Icons to the Left Like in Google Maps App
How to Show a Marker in Maps Launched by Geo Uri Intent
How to Show One Layout on Top of the Other Programmatically in My Case
How Can a Divider Line Be Added in an Android Recyclerview
Full Text Search Example in Android
Image Size (Drawable-Hdpi/Ldpi/Mdpi/Xhdpi)
Android Preferences: How to Load the Default Values When the User Hasn't Used the Preferences-Screen
Accessing Google Account Id /Username via Android
Reusing Fragments in a Fragmentpageradapter
How to Change Line Color in Edittext
Android -- How to Position View Off-Screen