Android - Multiple database one project

Viewed 9034

I would like to ask if is possible to have multiple database under one project, with Room Persistence Library? Changing dynamic the selection of the database. Thanks

3 Answers

It is possible. Let's assume you have two sets of entities and two sets of DAOs. You can obtain access to two databases by:

  • creating two classes that extends RoomDatabase:

AppDatabase 1:

@Database(entities = {/*... the first set of entities ...*/}, version = 1)
public abstract class AppDatabase1 extends RoomDatabase {
    // the first set of DAOs
}

AppDatabase2:

@Database(entities = {/*... the second set of entities ...*/}, version = 1)
public abstract class AppDatabase2 extends RoomDatabase {
    // the second set of DAOs
}
  • instantiating the two databases:

Note that you'll use two different file names.

AppDatabase db1 = Room.databaseBuilder(getApplicationContext(), AppDatabase1.class, "database1.db").build();

AppDatabase db2 = Room.databaseBuilder(getApplicationContext(), AppDatabase2.class, "database2.db").build();

In this case, you can use both databases, but you won't be able to create queries between them. If you need to attach the two databases, then you should take a look at the link @Anees provided

You can reuse the entities and DAO if databases have the same schema and dynamically switch between them (helpful if you want to have a different database file for each user).

Entity class

@Entity
public class User {
    @PrimaryKey
    @NonNull
    public String uid;

    @ColumnInfo(name = "first_name")
    public String firstName;

    @ColumnInfo(name = "last_name")
    public String lastName;
}

DAO class

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
           "last_name LIKE :last LIMIT 1")
    User findByName(String first, String last);

    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);
}

Database class

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

DatabaseClient class

public class DatabaseClient {

  private Context mCtx;
  private AppDatabase appDatabase;
  private static String databaseName;
  private static DatabaseClient mInstance;


  private DatabaseClient(Context mCtx, String dbName) {
    this.mCtx = mCtx;

    if(databaseName == null || !databaseName.equalsIgnoreCase(dbName)) {
      databaseName = dbName;
    }
    appDatabase = Room.databaseBuilder(mCtx, AppDatabase.class, databaseName).build();
  }

  public String getDatabaseName() {
    return databaseName;
  }

  public static synchronized DatabaseClient getInstance(Context mCtx, String dbName) {
    if (mInstance == null || databaseName == null || !databaseName.equalsIgnoreCase(dbName)) {
      mInstance = new DatabaseClient(mCtx, dbName);
    }
    return mInstance;
  }

  public AppDatabase getAppDatabase() {
    return appDatabase;
  }
}

Now you can query based on a particular database by passing its name in the parameter in my case here let's say myDb

List<User> users = DatabaseClient.getInstance(getApplicationContext(), myDb).getAppDatabase().userDao().getAll()

Remember whenever you perform the first call with a database name, it creates the database file. If a new user arrives and calls to insert its info, it automatically creates a new database file and inserts the info data into it.

Just solved it with koin. I was creating an instant message app, and mutiple account login was required. After user1 logon my app, the databse name with im_id could be got, then by inject, I created the database with the id. Then user1 one logout , I just unload the datasource module and jump to the login activity. User2 then login, I reload the datasource module and created database for user2 with his im_id. Code as follows:

val dataSourceModule = module{
    single {
        Room.databaseBuilder(androidApplication(), AppDataBase::class.java, get<GsSelectedAndImTokenPersistence>().gsImToken?.gsImId ?: "im_database" )
            .build()
    }

    single { get<AppDataBase>().gsInfoDao() }
    single { get<AppDataBase>().gsGameInfoDao() }

    single { get<AppDataBase>().gameClientDao() }

    single { SharedPreferencesDataSourceImpl(androidContext()) } binds (
            arrayOf(
                ImDeviceIdPersistence::class,
                GsSelectedAndImTokenPersistence::class
            ))

}
 fun unLoadDataSourceModule() {
        unloadKoinModules(dataSourceModule)
    }

    fun reLoadDataSourceModule() {
        loadKoinModules(dataSourceModule)
    }

The interesting is, even if get().gsImToken?.gsImId is null, it won't create the default database with name "im_database" by using koin inject.

And This is where I create the database by inject ,after I got the im_id from server

 viewModel.gsImToken.observe(provideLifecycleOwner(), {
            ELogger.d("database initial","init database===")
            // Incase of the datasource module is not loaded by now
            KoinInitializer.reLoadDataSourceModule()
            val gs: AppDataBase by inject()
            gs.gsGameInfoDao().run {
                viewModel.initDao(this)
            }
        })

And the logout place:

class SettingViewModel(
  ...,
  val db: AppDataBase
): ViewModel() {
   ...
   fun onLogout(){ 
     ...
       db.close()
       KoinInitializer.unLoadDataSourceModule()
     ...
    }
}
Related