While upgrading my database schema I have run into a problem with a ForeignCollectionField (ormlite 4.7) not returning rows. With a clean install of the app, rows can be added and displayed as expected.
When the app is updated to a new version, the schema is updated (see below), but when rows are added to the database the collection is not returning the added rows. (I can see the rows exist in the database)
The parent row existed before the update. What do I need to do to fix it?
Parent class with Foreign Collection defined
#DatabaseTable(tableName = "setting")
public class SettingEntity {
#DatabaseField(generatedId = true)
private long id;
…
//New field added
#ForeignCollectionField
private ForeignCollection<DistributionEntity> distribution;
public SettingEntity() {
// Required for ORMLite
}
public ForeignCollection<DistributionEntity> getDistribution() {
return distribution;
}
public void setDistribution(ForeignCollection<DistributionEntity> distribution) {
this.distribution = distribution;
}
}
Child class
#DatabaseTable(tableName = "distribution")
public class DistributionEntity {
#DatabaseField(generatedId = true)
private long id;
…
//New field added
#DatabaseField(canBeNull = true, foreign = true, index = true, foreignAutoRefresh = true, columnDefinition = "integer references setting(id) on delete cascade")
private SettingEntity setting;
public void setSetting(SettingEntity setting) {
this.setting = setting;
}
}
onUpgrade code
RuntimeExceptionDao<DistributionEntity, Integer> distributionDao = helper.getDistributionDao();
distributionDao.executeRaw("ALTER TABLE distribution ADD setting_id");
distributionDao.executeRaw("CREATE INDEX distribution_setting_idx ON distribution (setting_id)");
Debug info of the ForeignCollectionField call distribution
The code that iterates over the collection
public ArrayList<Distribution> getDistribution() {
getEntity().getDistribution().size();
final ArrayList<Distribution> items = new ArrayList<Distribution>();
final ForeignCollection<DistributionEntity> collection = getEntity().getDistribution();
for (final DistributionEntity item : collection) {
final Distribution dist = new Distribution(item, mContext);
items.add(dist);
}
return items;
}
NB getEntity() returns an instance of SettingEntity
Thanks for spending the time
More of a workaround than answer but had to get around this problem. Replicated behavior by writing code.
public List<DistributionEntity> getDistribution() {
List<DistributionEntity> distributionEntities = new ArrayList<DistributionEntity>();
try {
DatabaseHelper helper = DatabaseHelper.getInstance();
RuntimeExceptionDao<DistributionEntity, Integer> dao = helper.getDistributionDao();
QueryBuilder<DistributionEntity, Integer> queryBuilder = dao.queryBuilder();
queryBuilder.where().eq(DistributionEntity.SETTING_FIELD_NAME, Long.toString(this.getId()));
PreparedQuery<DistributionEntity> preparedQuery = queryBuilder.prepare();
distributionEntities = dao.query(preparedQuery);
} catch (SQLException e) {
e.printStackTrace();
}
return distributionEntities;
//return distribution;
}
Love to know what the true answer is
Related
Any thoughts, suggestions, or ideas on best practice or option for the following?
Problem: With Android Room, what is the practical way to approach creating and handling multiple databases in a single Android app?
What I'm trying to do: I have an Android app intended to manage multiple research topics. The idea is an user can create databases specific to subjects or research and able to store sources, attachments and notes on those subjects. For example, an user can have a database specific to the history of modern music, a second database on the subject of the history of hunting, and even subjects as deep as micro-biological research.
My thoughts were to have separate databases vs. one database that stores all this data. Especially since attachments can be stored and take up space quite quickly. And these databases can be shared between the phone / tablet app and a desktop version. There is a Java desktop version of this being used.
What I've Done" I've really only searched here and googled some but appear kind-of vague. I'm familiar with and have migrated changes to a database but wasn't sure if this would always be the best way to create a new database, as well as renaming, etc.
This Android app comes with a predefined and pre-popuated database as a demonstration. This database hasn't changed for 2 years now. So, the idea was possibly having a "template.db" that could be used to create new databases and rename them accordingly.
With Android Room, what is the practical way to approach creating and handling multiple databases in a single Android app?
You can certainly handle multiple databases and multiple databases based upon the same schema.
The issue is how to ascertain what databases there are that can be used. If all the databases were located in the same path (or even multiple paths) then this could be used. Another methodology could be to have a database of the databases.
Here's an example that utilises the databases of databases (the "MasterDatabase") and allows access to x databases.
So first the MasterDatabase which has a simple table with an id column (could be dispensed with) and a column for the database name. The table (#Entity) being named MasterDatabaseList as per :-
#Entity(
indices = { #Index(value = "databaseName", unique = true)
}
)
class MasterDatabaseList {
#PrimaryKey
Long id;
String databaseName;
public MasterDatabaseList() {}
#Ignore
public MasterDatabaseList(String databaseName) {
this.databaseName = databaseName;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDatabaseName() {
return databaseName;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
}
Note the unique index on the databaseName column
Accompanying the table is MasterDao an #Dao class :-
#Dao
abstract class MasterDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(MasterDatabaseList masterDatabaseList);
#Query("SELECT * FROM masterdatabaselist")
abstract List<MasterDatabaseList> getAllDatabases();
}
allows rows to be inserted or extracted.
a duplicated database will be ignored and thus not added.
MasterDatabase is the #Database class (which ties the previous classes to the database) and includes a method to get an instance of the database from which the MasterDao can be accessed :-
#Database(
entities = {MasterDatabaseList.class},
version = 1
)
abstract class MasterDatabase extends RoomDatabase {
abstract MasterDao getMasterDao();
static volatile MasterDatabase instance = null;
public static MasterDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context,MasterDatabase.class,"master.db")
.allowMainThreadQueries()
.build();
}
return instance;
}
}
Now the template database, Base???? (a simple single table database for the demo). First the table BaseTable #Entity class:-
#Entity
class BaseTable {
#PrimaryKey
Long id;
String mydata;
public BaseTable(){}
#Ignore
public BaseTable(String myData) {
this.mydata = myData;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMydata() {
return mydata;
}
public void setMydata(String mydata) {
this.mydata = mydata;
}
}
a very simple table with an id column and a column that holds some string data.
Accompanying is the #Dao class BaseDao :-
#Dao
abstract class BaseDao {
#Insert
abstract long insert(BaseTable baseTable);
#Query("SELECT * FROM basetable")
abstract List<BaseTable> getAllBaseTables();
#Update
abstract int update(BaseTable baseTable);
}
with very basic insert, extract and update
and as before the #Database class BaseDatabase :-
#Database(
entities = {BaseTable.class},
version = 1
)
abstract class BaseDatabase extends RoomDatabase {
abstract BaseDao getBaseDao();
public static BaseDatabase getInstance(Context context, String databaseName) {
BaseDatabase instance = null;
if (databaseName != null) {
return Room.databaseBuilder(context, BaseDatabase.class, databaseName)
.allowMainThreadQueries()
.build();
}
return instance;
}
}
note how the database name needs to be passed, that is basically the crux of catering for multiple databases.
With all of that a Demo Activity.
public class MainActivity extends AppCompatActivity {
private static final String TAG = "DBINFO";
MasterDatabase masterDB;
MasterDao masterDao;
/* 3 Lists that need to be synchronised index wise */
/* i.e. each index position should hold the respective name/databaseobject/dao
/* List of the known databases (their names) */
List<MasterDatabaseList> masterDatabaseListList = null;
/* List of the BaseDatabase objects */
ArrayList<BaseDatabase> baseDatabaseList = new ArrayList<>();
/* List of the BaseDao's */
ArrayList<BaseDao> baseDaoList = new ArrayList<>();
/* The current database */
int currentBaseIndex = -1; /* Index into the three Lists */
BaseDatabase currentDB = null;
BaseDao currentDao = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
masterDB = MasterDatabase.getInstance(this);
masterDao = masterDB.getMasterDao();
masterDatabaseListList = masterDao.getAllDatabases();
// Add default db1 if it does not exist
if (masterDatabaseListList.size() < 1) {
addBaseDB("db1");
}
buildBaseLists();
/* Add some data to db1 IF it exists (it should) */
setCurrentIndexDBandDao("db1");
if (currentBaseIndex > -1) {
currentDao.insert(new BaseTable("Blah for db1"));
}
/* Add some data to db2 (it will not exist) */
/* noting that the database will be created if it does not exist */
setCurrentIndexDBandDao("db2");
if (currentBaseIndex == -1) {
addBaseDB("db2");
}
if (currentBaseIndex > -1) {
currentDao.insert(new BaseTable("Blah for db2"));
}
/* Extract and Log Data for ALL the BaseDatabase databases i.e. db1 and db2 */
for(MasterDatabaseList masterdb: masterDao.getAllDatabases()) {
Log.d(TAG,"Database is " + masterdb.getDatabaseName());
setCurrentIndexDBandDao(masterdb.databaseName);
if (currentBaseIndex > -1) {
for(BaseTable bt: currentDao.getAllBaseTables()) {
Log.d(TAG,"Extracted Base Table row where MyData is" + bt.getMydata());
}
}
}
}
/* Add a new Database */
/* Note that it assumes that it will now be the current */
/* so the current values are set */
private void addBaseDB(String baseDBName) {
masterDao.insert(new MasterDatabaseList(baseDBName));
buildBaseLists();
setCurrentIndexDBandDao(baseDBName);
}
/* Build/ReBuild the 3 Lists according to the master database */
/* This could be better managed so as to not rebuild existing database/dao objects */
private void buildBaseLists() {
int ix = 0;
baseDatabaseList.clear();
baseDaoList.clear();
masterDatabaseListList = masterDao.getAllDatabases();
// Loop through the databases defined in the master database adding the database and dao to the respective lists
for (MasterDatabaseList masterDB: masterDao.getAllDatabases()) {
BaseDatabase baseDB = BaseDatabase.getInstance(this, masterDB.getDatabaseName());
baseDatabaseList.add(baseDB);
baseDaoList.add(baseDB.getBaseDao());
ix++;
}
}
/* Set the current trio according to the database name that is:*/
/* 1.the currentBaseIndex for the 3 Lists */
/* 2. the BaseDatabase object */
/* 3. the BaseDao */
/* The index value (currentBaseIndex) is also returned */
private int setCurrentIndexDBandDao(String baseDBName) {
currentBaseIndex = getListIndexByBaseDBName(baseDBName);
if (currentBaseIndex > -1) {
currentDB = baseDatabaseList.get(currentBaseIndex);
currentDao = baseDaoList.get(currentBaseIndex);
}
return currentBaseIndex;
}
/* Get the index according to the database name passed */
/* note -1 signifies not know/found */
private int getListIndexByBaseDBName(String baseDBName) {
masterDatabaseListList = masterDao.getAllDatabases(); // OverKill????
int rv = -1; // default to not found
for(int i=0; i < masterDatabaseListList.size();i++) {
if (masterDatabaseListList.get(i).databaseName.equals(baseDBName)) {
rv = i;
break;
}
}
return rv;
}
/* Output all rows from the BaseTable for data extracted by the BaseDaos getAllBaseTables */
private void logBaseData(List<BaseTable> baseTableList) {
Log.d(TAG,"Current Database Index is " + currentBaseIndex + " DB name is " + masterDatabaseListList.get(currentBaseIndex).getDatabaseName());
for(BaseTable bt: baseTableList) {
Log.d(TAG,"\tMyData value is " + bt.getMydata());
}
}
}
Result
When the above is run for the first time the log includes:-
2021-09-16 11:39:30.262 D/DBINFO: Database is db1
2021-09-16 11:39:30.278 D/DBINFO: Extracted Base Table row where MyData isBlah for db1
2021-09-16 11:39:30.278 D/DBINFO: Database is db2
2021-09-16 11:39:30.284 D/DBINFO: Extracted Base Table row where MyData isBlah for db2
And via Android Studio's App Inspector the databases :-
and for the db2 BaseTable :-
Note The above is only intended to cover the basics of utilising multiple databases in what is intended to be a simplistic explanation, as such the code has been kept short and simple. It would probably be unacceptable, as it is, for an App that would be distribtued.
I have a RealmObject, which is used as a temporary data cache only (there will be many entries). I also wrote a static method add() so I can easily add a new entry, but it seems too complicated. Here is the whole class:
public class ExchangePairPriceCache extends RealmObject {
#Index
private String exchangeName;
#Index
private String baseCurrency;
#Index
private String quoteCurrency;
private float price;
private long lastPriceUpdate;
public ExchangePairPriceCache() {
exchangeName = "";
baseCurrency = "";
quoteCurrency = "";
price = 0;
lastPriceUpdate = 0;
}
public ExchangePairPriceCache(String exchangeName, String baseCurrency, String quoteCurrency) {
this.exchangeName = exchangeName;
this.baseCurrency = baseCurrency;
this.quoteCurrency = quoteCurrency;
price = 0;
lastPriceUpdate = 0;
}
public void setPrice(float price) {
// this needs to be called inside a Realm transaction if it's a managed object
this.price = price;
lastPriceUpdate = System.currentTimeMillis();
}
public float getPrice() {
return price;
}
/* static functions */
public static void add(String exchangeName, String baseCurrency, String quoteCurrency, float price) {
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(r -> {
ExchangePairPriceCache priceCache = r.where(ExchangePairPriceCache.class)
.equalTo("exchangeName", exchangeName)
.equalTo("baseCurrency", baseCurrency)
.equalTo("quoteCurrency", quoteCurrency).findFirst();
if(priceCache != null) {
priceCache.setPrice(price);
} else {
priceCache = new ExchangePairPriceCache(exchangeName, baseCurrency, quoteCurrency);
priceCache.setPrice(price);
ExchangePairPriceCache finalPriceCache = priceCache;
r.insert(finalPriceCache);
}
});
realm.close();
}
public static ExchangePairPriceCache get(String exchangeName, String baseCurrency, String quoteCurrency) {
Realm realm = Realm.getDefaultInstance();
ExchangePairPriceCache priceCache = realm.where(ExchangePairPriceCache.class)
.equalTo("exchangeName", exchangeName)
.equalTo("baseCurrency", baseCurrency)
.equalTo("quoteCurrency", quoteCurrency)
.greaterThan("lastPriceUpdate", System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(10)).findFirst();
if(priceCache != null)
priceCache = realm.copyFromRealm(priceCache);
realm.close();
return priceCache;
}
public static void deleteAll() {
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(r -> r.delete(ExchangePairPriceCache.class));
realm.close();
}
}
Questions:
Is this a good design (having static functions for ease of use)? I like how I can insert new entries into cache like ExchangePairPriceCache.add("NASDAQ", "AAPL", "USD", 100.5); and delete all with ExchangePairPriceCache.deleteAll() when needed.
How can I simplify add() function? Right now I check if entry already exists and then update the price and if it doesn't, I create a new object and insert it into Realm. I am not able to use updateOrInsert because I don't have unique index for object.
Maybe I am just questioning myself too much and this is all good as it is. But I'd really appreciate some input from experts who use it daily.
You should use a "Repository design pattern" with a DAO object (Data Access Object), to do all your read/ write transactions in realm.
Model class should be a blind copy of objects just holding entities.
Since you do not have any unique identifiers, you can try below
Cache the Exchange pair in Shared preferences file (if they are added earlier or not)
For faster read/writes : Create a temporary unique identifier with a combination of key-value pair that you already have
eg : (exchangeName + baseCurrency + quoteCurrency) - Cast into proper formats to create some unique key with all these values.
After using Android Room for a few weeks now and getting the hang of basic queries, I've run into an issue with attempting to update a list of custom objects. For some reason when Room tries to create the SQLLite string to insert my new data, it gets stuck with the placeholders:
From the debug window:
Caused by: android.database.sqlite.SQLiteException: near "?": syntax error (code 1): , while compiling: UPDATE player_characters SET ability_scores = ?,?,?,?,?,? WHERE playerCharacterID = ?
#################################################################
Error Code : 1 (SQLITE_ERROR)
Caused By : SQL(query) error or missing database.
(near "?": syntax error (code 1): , while compiling: UPDATE player_characters SET ability_scores = ?,?,?,?,?,? WHERE playerCharacterID = ?)
#################################################################
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:1005)
at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:570)
at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
at android.database.sqlite.SQLiteProgram.(SQLiteProgram.java:59)
at android.database.sqlite.SQLiteStatement.(SQLiteStatement.java:31)
at android.database.sqlite.SQLiteDatabase.compileStatement(SQLiteDatabase.java:1375)
at android.arch.persistence.db.framework.FrameworkSQLiteDatabase.compileStatement(FrameworkSQLiteDatabase.java:62)
at android.arch.persistence.room.RoomDatabase.compileStatement(RoomDatabase.java:204)
at com.pathfinderstattracker.pathfindercharactersheet.database.database_daos.PlayerCharacterDao_Impl.updatePlayerCharacterAbilityScores(PlayerCharacterDao_Impl.java:321)
The DAO that contains the query:
#Dao
#TypeConverters({UUIDConverter.class,
AbilityScoreConcreteConverter.class})
public interface PlayerCharacterDao
{
#Query("UPDATE player_characters "+
"SET ability_scores = :playerCharacterAbilityScores "+
"WHERE playerCharacterID = :characterIDToUpdate")
void updatePlayerCharacterAbilityScores(UUID characterIDToUpdate, List<AbilityScore> playerCharacterAbilityScores);
}
And the repository command that calls it:
private static class updatePlayerCharacterAbilityScoresAsyncTask extends AsyncTask<Object, Void, Void>
{
private PlayerCharacterDao asyncPlayerCharacterDao;
updatePlayerCharacterAbilityScoresAsyncTask(PlayerCharacterDao dao) {asyncPlayerCharacterDao = dao;}
#Override
protected Void doInBackground(final Object... params)
{
UUID playerCharacterID = (UUID)params[0];
List<AbilityScore> updatedAbilityScores = (ArrayList<AbilityScore>)params[1];
asyncPlayerCharacterDao.updatePlayerCharacterAbilityScores(playerCharacterID, updatedAbilityScores);
return null;
}
}
I can confirm that the data is getting to the room query properly, and I've tried passing both concrete and interface objects into the query, as well as had a converter for both individual AbilityScore objects and a list of AbilityScore objects. Any help would be greatly appreciated!
EDIT: A few people have requested the entity that's being updated:
#Entity(tableName = "player_characters")
#TypeConverters({AlignmentEnumConverter.class,
HitPointsConverter.class,
DamageReductionConverter.class,
StringListConverter.class,
UUIDConverter.class,
StringListConverter.class,
AbilityScoreListConverter.class,
CombatManeuverConverter.class})
public class PlayerCharacterEntity
{
#PrimaryKey
#NonNull
private UUID playerCharacterID;
#ColumnInfo(name="character_name")
private String playerCharacterName;
#ColumnInfo(name="character_level")
private int characterLevel;
#ColumnInfo(name="concentration_check")
private int concentrationCheck;
#ColumnInfo(name="character_alignment")
private AlignmentEnum characterAlignment;
#ColumnInfo(name="total_base_attack_bonus")
private int totalBaseAttackBonus;
#ColumnInfo(name="total_hit_points")
private IHitPoints totalHitPoints;
#ColumnInfo(name="total_ac")
private int totalAC;
#ColumnInfo(name="damage_reduction")
private IDamageReduction damageReduction;
#ColumnInfo(name="languages_known")
private List<String> languagesKnown;
#ColumnInfo(name="ability_scores")
private List<IAbilityScore> abilityScores;
#ColumnInfo(name="combat_Maneuver_stats")
private ICombatManeuver combatManeuverStats;
#ColumnInfo(name="spell_resistance")
private int spellResistance;
#ColumnInfo(name="initiative")
private int initiative;
#ColumnInfo(name="fortitude_save")
private int fortitudeSave;
#ColumnInfo(name="reflex_save")
private int reflexSave;
#ColumnInfo(name="will_save")
private int willSave;
~Getters/Setters and Constructors removed for brevity~
}
EDIT: And for good measure I thought I would include the #TypeConverter for AbilityScore (I've reverted this to an earlier form that uses interfaces rather than concrete, since that works elsewhere in the code and difference didn't seem to change anything):
public class AbilityScoreConverter
{
#TypeConverter
public IAbilityScore fromString(String value)
{
IAbilityScore formattedAbilityScore = new AbilityScore();
String[] tokens = value.split(" ");
formattedAbilityScore.setAmount(Integer.parseInt(tokens[0]));
switch(tokens[1])
{
case "STR":
formattedAbilityScore.setStat(AbilityScoreEnum.STR);
case "DEX":
formattedAbilityScore.setStat(AbilityScoreEnum.DEX);
case "CON":
formattedAbilityScore.setStat(AbilityScoreEnum.CON);
case "INT":
formattedAbilityScore.setStat(AbilityScoreEnum.INT);
case "WIS":
formattedAbilityScore.setStat(AbilityScoreEnum.WIS);
case "CHA":
formattedAbilityScore.setStat(AbilityScoreEnum.CHA);
default:
//This may cause issues down the line if a non existent enum gets in the db somehow, but we don't have any error handling yet
//Todo: Add error handling
formattedAbilityScore.setStat(AbilityScoreEnum.STR);
}
return formattedAbilityScore;
}
#TypeConverter
public String toString(IAbilityScore value)
{
return value.toString();
}
}
EDIT: I've cleaned up the logcat text to focus just on the Room/SQLLite issues.
After some searching I was unfortunately forced to give up on updating my db using a #Query command and instead had to fall back on using Rooms default #Update notation. While this does work and properly updates the data in the database, it doesn't allow for me to only update certain fields.
Using ArrayList instead of List in the UPDATE query works for me.
I've followed the solution.
The difference in the Impl build :
MutableList:
#Override
public void test(int tkID, List<Boolean> test) {
StringBuilder _stringBuilder = StringUtil.newStringBuilder();
_stringBuilder.append("UPDATE TasksTable SET test = ");
final int _inputSize = test.size();
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
_stringBuilder.append(" WHERE taskID = ");
_stringBuilder.append("?");
final String _sql = _stringBuilder.toString();
SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
ArrayList:
#Override
public void test(int tkID, ArrayList<Boolean> test) {
final SupportSQLiteStatement _stmt = __preparedStmtOfTest.acquire();
__db.beginTransaction();
try {
int _argIndex = 1;
final String _tmp;
_tmp = Converters.listBooleanToString(test);
if (_tmp == null) {
_stmt.bindNull(_argIndex);
} else {
_stmt.bindString(_argIndex, _tmp);
}
_argIndex = 2;
_stmt.bindLong(_argIndex, tkID);
_stmt.executeUpdateDelete();
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
__preparedStmtOfTest.release(_stmt);
}
}
As you can see, the ArrayList uses the converter while the MutableList does not.
I have 2 databases in the project, one of them created when I open the app, the other is provided with the assets.
When DaoSession is generated the DaoSession is created for all models.
also the Schema in the gradle file is used for both databases
How can I differentiate between the 2 databases and their schemas ?
You need to create two different classes which extends org.greenrobot.greendao.database.DatabaseOpenHelper. These two different classes DevOpenHelperForDatabase1 and DevOpenHelperForDatabase2
will handle db realted I/o. It is versy easy to understand from the below code to create two different database with same and different schema or table or entities:
public class App extends Application {
private DaoSessionForDatabase1 mDaoSessionForDatabase1;
private DaoSessionForDatabase2 mDaoSessionForDatabase2;
#Override
public void onCreate() {
super.onCreate();
//Create Doa session for database1
DevOpenHelperForDatabase1 devOpenHelperForDatabase1 = new DevOpenHelperForDatabase1(this,
"database1-db");
Database databse1 = devOpenHelperForDatabase1.getWritableDb();
mDaoSessionForDatabase1 = new DaoMasterForDatabase1(databse1).newSession();
//Create Doa session for database2
DevOpenHelperForDatabase2 devOpenHelperForDatabase2 = new DevOpenHelperForDatabase2(this,
"database2-db");
Database databse2 = devOpenHelperForDatabase2.getWritableDb();
mDaoSessionForDatabase2 = new DaoMasterForDatabase2(databse2).newSession();
}
public DaoSessionForDatabase1 getDaoSessioForDatabase1() {
return mDaoSessionForDatabase1;
}
public DaoSessionForDatabase2 getDaoSessioForDatabase2() {
return mDaoSessionForDatabase2;
}
}
You can access same and different schema or table or entities as bellow from Activity as an example:
// get the Schema1 DAO for Database1
DaoSessionForDatabase1 daoSessionForDatabase1 = ((App) getApplication()).getDaoSessioForDatabase1();
Schema1Dao schema1Dao = daoSessionForDatabase1.getSchema1Dao();
// get the Schema2 DAO for Database2
DaoSessionForDatabase2 daoSessionForDatabase2 = ((App) getApplication()).getDaoSessioForDatabase2();
Schema2Dao schema2Dao = daoSessionForDatabase2.getSchema2Dao();
Update 2:: The above can be discarded but the approach will be the same. Update done based on the discussion in the comments below:
I made the changes in the example greenDAO -> examples
package org.greenrobot.greendao.example;
import android.app.Application;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.example.DaoMaster.DevOpenHelper;
public class App extends Application {
/** A flag to show how easily you can switch from standard SQLite to the encrypted SQLCipher. */
public static final boolean ENCRYPTED = true;
private DaoSession daoSession;
private DaoSession daoSession1;
#Override
public void onCreate() {
super.onCreate();
DevOpenHelper helper = new DevOpenHelper(this, ENCRYPTED ? "notes-db-encrypted" : "notes-db");
Database db = ENCRYPTED ? helper.getEncryptedWritableDb("super-secret") : helper.getWritableDb();
daoSession = new DaoMaster(db).newSession();
DevOpenHelper helper1 = new DevOpenHelper(this, "notes1-db");
Database db1 = helper1.getWritableDb();
daoSession1 = new DaoMaster(db1).newSession();
}
public DaoSession getDaoSession() {
return daoSession;
}
public DaoSession getDaoSession1() {
return daoSession1;
}
}
Now make the following chnages in NoteActivity.java
//Add below class members
private static boolean switchDbBetweenOneAndTwo = false;
private NoteDao noteDao2;
private Query<Note> notesQuery2;
//In on craete add the following as the last statement after notesQuery = noteDao.queryBuilder().orderAsc(NoteDao.Properties.Text).build();
#Override
public void onCreate(Bundle savedInstanceState) {
......
Log.d("Database 1", "notesQuery.list()="+notesQuery.list().toString());
// get the note DAO for Database2
DaoSession daoSessionForDb2 = ((App) getApplication()).getDaoSession1();
noteDao2 = daoSessionForDb2.getNoteDao();
// query all notes, sorted a-z by their text
notesQuery2 = noteDao2.queryBuilder().orderAsc(NoteDao.Properties.Text).build();
Log.d("Database 2", "notesQuery2.list()="+notesQuery2.list().toString());
updateNotes();
}
//Replace updateNotes as
private void updateNotes() {
List<Note> notes = notesQuery.list();
List<Note> notes2 = notesQuery2.list();
notes.addAll(notes2);
notesAdapter.setNotes(notes);
}
//Replace addNote as
private void addNote() {
String noteText = editText.getText().toString();
editText.setText("");
final DateFormat df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
String comment = "Added on " + df.format(new Date());
Note note = new Note();
note.setText(noteText);
note.setComment(comment);
note.setDate(new Date());
note.setType(NoteType.TEXT);
if(!switchDbBetweenOneAndTwo){
note.setText(noteText + " In database 1");
noteDao.insert(note);
}
else {
note.setText(noteText + " In database 2");
noteDao2.insert(note);
}
Log.d("DaoExample", "Inserted new note, ID: " + note.getId());
switchDbBetweenOneAndTwo = true;
updateNotes();
}
I made no changes in gradle file or added anything as it dosen't make any sense to me.
For Greendao Version 3, it is not possible to have more than 1 schema.
github.com/greenrobot/greenDAO/issues/356
As they wrote on their website also:
Note that multiple schemas are currently not supported when using the
Gradle plugin . For the time being, continue to use your generator
project.
They have added this feature yet to the new GreenDao.
I have a Client bean ,
#DatabaseField(columnName = "client_id",generatedId = true,useGetSet = true)
private Integer clientId;
#DatabaseField(columnName = "client_nom",useGetSet = true)
private String clientNom;
#DatabaseField(columnName = "city_id",foreign = true,useGetSet = true)
private City city;
and a City bean ,
#DatabaseField(columnName = "city_id",generatedId = true,useGetSet = true)
private Integer cityId;
#DatabaseField(columnName = "city_name",useGetSet = true)
private String cityName;
#ForeignCollectionField
private ForeignCollection<Client> clientList;
Those beans are just an example but let's say , I want to delete all the clients having as foreign city cityId when deleting a city.
How is that possible please ?
ORMLite does not support cascading deletes #Majid. That is currently outside of what it considers to be "lite". If you delete the city then you need to delete the clients by hand.
One way to ensure this would be to have a CityDao class that overrides the delete() method and issues the delete through the ClientDao at the same time. Something like:
public class CityDao extends BaseDaoImpl<City, Integer> {
private ClientDao clientDao;
public CityDao(ConnectionSource cs, ClientDao clientDao) {
super(cs, City.class);
this.clientDao = clientDao;
}
...
#Override
public int delete(City city) {
// first delete the clients that match the city's id
DeleteBuilder db = clientDao.deleteBuilder();
db.where().eq("city_id", city.getId());
clientDao.delete(db.prepare());
// then call the super to delete the city
return super.delete(city);
}
...
}
To implement cascading while using ORMLite on Android you need to enable foreign key restraints as described here:
(API level > 16)
#Override
public void onOpen(SQLiteDatabase db){
super.onOpen(db);
if (!db.isReadOnly()){
db.setForeignKeyConstraintsEnabled(true);
}
}
For API level < 16 please read:
Foreign key constraints in Android using SQLite? on Delete cascade
Then use columnDefinition annotation to define cascading deletes. Ex:
#DatabaseField(foreign = true,
columnDefinition = "integer references my_table(id) on delete cascade")
private MyTable table;
This is assuming the table/object name is "my_table", as described here: Creating foreign key constraints in ORMLite under SQLite