I have kind of a quiz app and I have a database with all the questions in tables, for each question there is a column solved that I update if the answer was correct, so I can filter with SQL WHERE to only show unsolved questions.
Now every once in a while I have to correct typos in the questions or might want to add some new ones, so
How do I employ the corrected database (questions.db) from the assets to the saved one on the user device while keeping the solved columns?
I thought of and tried the following things without success:
Currently, i use a self-crafted solution to replace the database on the device (destructive) but between updates keep the solved info https://github.com/ueen/RoomAsset
Put solved info (question id solved y/n) in a separate table and LEFT JOIN to filter unsolved questions, this only complicated matters
Have an extra database for the solved questions, it seems there's no easy way to attach two Room Databases
So in essence, this may be inspiration for the Room dev team, I would like to have a proper migration strategy for createFromAsset with ability to specify certain columns/tables to be kept.
Thanks for your great work so far, I really enjoy using Android Jetpack and Room especially!
Also, I'm happy about any workaround I could employ to resolve this issue :)
I believe the following does what you want
#Database(version = DatabaseConstants.DBVERSION, entities = {Question.class})
public abstract class QuestionDatabase extends RoomDatabase {
static final String DBNAME = DatabaseConstants.DBNAME;
abstract QuestionDao questionsDao();
public static QuestionDatabase getInstance(Context context) {
copyFromAssets(context,false);
if (getDBVersion(context,DatabaseConstants.DBNAME) < DatabaseConstants.DBVERSION) {
copyFromAssets(context,true);
}
return Room.databaseBuilder(context,QuestionDatabase.class,DBNAME)
.addCallback(callback)
.allowMainThreadQueries()
.addMigrations(Migration_1_2)
.build();
}
private static RoomDatabase.Callback callback = new Callback() {
#Override
public void onCreate(#NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
}
#Override
public void onOpen(#NonNull SupportSQLiteDatabase db) {
super.onOpen(db);
}
#Override
public void onDestructiveMigration(#NonNull SupportSQLiteDatabase db) {
super.onDestructiveMigration(db);
}
};
private static Migration Migration_1_2 = new Migration(1, 2) {
#Override
public void migrate(#NonNull SupportSQLiteDatabase database) {
}
};
private static boolean doesDatabaseExist(Context context) {
if (new File(context.getDatabasePath(DBNAME).getPath()).exists()) return true;
if (!(new File(context.getDatabasePath(DBNAME).getPath()).getParentFile()).exists()) {
new File(context.getDatabasePath(DBNAME).getPath()).getParentFile().mkdirs();
}
return false;
}
private static void copyFromAssets(Context context, boolean replaceExisting) {
boolean dbExists = doesDatabaseExist(context);
if (dbExists && !replaceExisting) return;
//First Copy
if (!replaceExisting) {
copyAssetFile(context);
return;
}
//Subsequent Copies
File originalDBPath = new File(context.getDatabasePath(DBNAME).getPath());
// Open and close the original DB so as to checkpoint the WAL file
SQLiteDatabase originalDB = SQLiteDatabase.openDatabase(originalDBPath.getPath(),null,SQLiteDatabase.OPEN_READWRITE);
originalDB.close();
//1. Rename original database
String preservedDBName = "preserved_" + DBNAME;
File preservedDBPath = new File (originalDBPath.getParentFile().getPath() + preservedDBName);
(new File(context.getDatabasePath(DBNAME).getPath()))
.renameTo(preservedDBPath);
//2. Copy the replacement database from the assets folder
copyAssetFile(context);
//3. Open the newly copied database
SQLiteDatabase copiedDB = SQLiteDatabase.openDatabase(originalDBPath.getPath(),null,SQLiteDatabase.OPEN_READWRITE);
SQLiteDatabase preservedDB = SQLiteDatabase.openDatabase(preservedDBPath.getPath(),null,SQLiteDatabase.OPEN_READONLY);
//4. get the orignal data to be preserved
Cursor csr = preservedDB.query(
DatabaseConstants.QUESTION_TABLENAME,DatabaseConstants.EXTRACT_COLUMNS,
null,null,null,null,null
);
//5. Apply preserved data to the newly copied data
copiedDB.beginTransaction();
ContentValues cv = new ContentValues();
while (csr.moveToNext()) {
cv.clear();
for (String s: DatabaseConstants.PRESERVED_COLUMNS) {
switch (csr.getType(csr.getColumnIndex(s))) {
case Cursor.FIELD_TYPE_INTEGER:
cv.put(s,csr.getLong(csr.getColumnIndex(s)));
break;
case Cursor.FIELD_TYPE_STRING:
cv.put(s,csr.getString(csr.getColumnIndex(s)));
break;
case Cursor.FIELD_TYPE_FLOAT:
cv.put(s,csr.getDouble(csr.getColumnIndex(s)));
break;
case Cursor.FIELD_TYPE_BLOB:
cv.put(s,csr.getBlob(csr.getColumnIndex(s)));
break;
}
}
copiedDB.update(
DatabaseConstants.QUESTION_TABLENAME,
cv,
DatabaseConstants.QUESTION_ID_COLUMN + "=?",
new String[]{
String.valueOf(
csr.getLong(
csr.getColumnIndex(DatabaseConstants.QUESTION_ID_COLUMN
)
)
)
}
);
}
copiedDB.setTransactionSuccessful();
copiedDB.endTransaction();
csr.close();
//6. Cleanup
copiedDB.close();
preservedDB.close();
preservedDBPath.delete();
}
private static void copyAssetFile(Context context) {
int buffer_size = 8192;
byte[] buffer = new byte[buffer_size];
int bytes_read = 0;
try {
InputStream fis = context.getAssets().open(DBNAME);
OutputStream os = new FileOutputStream(new File(context.getDatabasePath(DBNAME).getPath()));
while ((bytes_read = fis.read(buffer)) > 0) {
os.write(buffer,0,bytes_read);
}
os.flush();
os.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("Unable to copy from assets");
}
}
private static int getDBVersion(Context context, String databaseName) {
SQLiteDatabase db = SQLiteDatabase.openDatabase( context.getDatabasePath(databaseName).getPath(),null,SQLiteDatabase.OPEN_READONLY);
int rv = db.getVersion();
db.close();
return rv;
}
}
This manages the Asset File copy (in this case directly from the assets folder) outside of Room and before the database is built doing it's own version and database existence checking. Although ATTACH could be used, the solution keeps the original and the new databases seperate when updating the new using a Cursor.
Some flexibility/adaptability has been included in that the columns to be preserved can be expanded upon. In the test runs DatabaseConstants includes :-
public static final String[] PRESERVED_COLUMNS = new String[]
{
QUESTION_SOLVED_COLUMN
};
public static final String[] EXTRACT_COLUMNS = new String[]
{
QUESTION_ID_COLUMN,
QUESTION_SOLVED_COLUMN
};
thus additional columns to be preserved can be added (of any type as per 5. in the copyFromAssets method).
The columns to be extracted can also be specified, in the case above, the ID column uniquely identifies the question so that is extracted in addition to the solved column for use by the WHERE clause.
Testing
The above has been tested to :-
Original
Copy the first version of the database from the assets when DBVERSION is 1.
Note that this originaly contains 3 questions as per
Part of the code (in the invoking activity checks to to see if all the solved values are 0 if so, then it chages the solved status of the question with an id of 2)
Not copy the database, but use the existing database when DBVERSION is 1 on a subseuent run(s).
ID 2 remains solved.
New
After renaming the original asset from to be prefixed with original_, editing the database to be as below and after copying it to the assets file :-
Without changing the DBVERSION (still 1) run and the original database is still in use.
After changing DBVERSION to 2 running copies the changed asset file and restores/preserves the solved status.
For subsequent runs the solved status for the new data remains.
For testing the invoking activity consisted of :-
public class MainActivity extends AppCompatActivity {
QuestionDatabase questionDatabase;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
questionDatabase = QuestionDatabase.getInstance(this);
int solvedCount = 0;
for (Question q: questionDatabase.questionsDao().getAll()) {
if (q.isSolved()) solvedCount++;
q.logQuestion();
}
if (solvedCount == 0) {
questionDatabase.questionsDao().setSolved(true,2);
}
for (Question q: questionDatabase.questionsDao().getAll()) {
q.logQuestion();
}
}
}
For each run it outputs all of the questions to the log twice. After the first if there are no solved questions it solves the question with an id of 2.
The output from the last run was :-
2020-01-08 09:14:37.689 D/QUESTIONINFO: ID is 1 Question is Editted What is x
Answers Are :-
a
b
x
Correct Answer is 3
Is Solved false
2020-01-08 09:14:37.689 D/QUESTIONINFO: ID is 2 Question is Edited What is a
Answers Are :-
a
b
c
Correct Answer is 1
Is Solved false
2020-01-08 09:14:37.689 D/QUESTIONINFO: ID is 3 Question is Edited What is b
Answers Are :-
a
b
c
Correct Answer is 2
Is Solved false
2020-01-08 09:14:37.689 D/QUESTIONINFO: ID is 4 Question is New Question What is d
Answers Are :-
e
f
d
Correct Answer is 3
Is Solved false
2020-01-08 09:14:37.692 D/QUESTIONINFO: ID is 1 Question is Editted What is x
Answers Are :-
a
b
x
Correct Answer is 3
Is Solved false
2020-01-08 09:14:37.692 D/QUESTIONINFO: ID is 2 Question is Edited What is a
Answers Are :-
a
b
c
Correct Answer is 1
Is Solved true
2020-01-08 09:14:37.692 D/QUESTIONINFO: ID is 3 Question is Edited What is b
Answers Are :-
a
b
c
Correct Answer is 2
Is Solved false
2020-01-08 09:14:37.693 D/QUESTIONINFO: ID is 4 Question is New Question What is d
Answers Are :-
e
f
d
Correct Answer is 3
Is Solved false
Additional - Improved Version
This is an approved version that caters for multiple tables and columns. To cater for tables a class TablePreserve has been added that allows a table, the columns to preserve, the columns to extract and the columns for the where clause. As per :-
public class TablePreserve {
String tableName;
String[] preserveColumns;
String[] extractColumns;
String[] whereColumns;
public TablePreserve(String table, String[] preserveColumns, String[] extractColumns, String[] whereColumns) {
this.tableName = table;
this.preserveColumns = preserveColumns;
this.extractColumns = extractColumns;
this.whereColumns = whereColumns;
}
public String getTableName() {
return tableName;
}
public String[] getPreserveColumns() {
return preserveColumns;
}
public String[] getExtractColumns() {
return extractColumns;
}
public String[] getWhereColumns() {
return whereColumns;
}
}
You create an Array of TablePreserve objects and they are looped through e.g.
public final class DatabaseConstants {
public static final String DBNAME = "question.db";
public static final int DBVERSION = 2;
public static final String QUESTION_TABLENAME = "question";
public static final String QUESTION_ID_COLUMN = "id";
public static final String QUESTION_QUESTION_COLUMN = QUESTION_TABLENAME;
public static final String QUESTION_ANSWER1_COLUMN = "answer1";
public static final String QUESTION_ANSWER2_COLUMN = "answer2";
public static final String QUESTION_ANSWER3_COLUMN = "answer3";
public static final String QUESTION_CORRECTANSWER_COLUMN = "correctAsnwer";
public static final String QUESTION_SOLVED_COLUMN = "solved";
public static final TablePreserve questionTablePreserve = new TablePreserve(
QUESTION_TABLENAME,
new String[]{QUESTION_SOLVED_COLUMN},
new String[]{QUESTION_ID_COLUMN,QUESTION_SOLVED_COLUMN},
new String[]{QUESTION_ID_COLUMN}
);
public static final TablePreserve[] TABLE_PRESERVELIST = new TablePreserve[] {
questionTablePreserve
};
}
Then QuestionsDatabase becomes :-
#Database(version = DatabaseConstants.DBVERSION, entities = {Question.class})
public abstract class QuestionDatabase extends RoomDatabase {
static final String DBNAME = DatabaseConstants.DBNAME;
abstract QuestionDao questionsDao();
public static QuestionDatabase getInstance(Context context) {
if (!doesDatabaseExist(context)) {
copyFromAssets(context,false);
}
if (getDBVersion(context, DatabaseConstants.DBNAME) < DatabaseConstants.DBVERSION) {
copyFromAssets(context, true);
}
return Room.databaseBuilder(context,QuestionDatabase.class,DBNAME)
.addCallback(callback)
.allowMainThreadQueries()
.addMigrations(Migration_1_2)
.build();
}
private static RoomDatabase.Callback callback = new Callback() {
#Override
public void onCreate(#NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
}
#Override
public void onOpen(#NonNull SupportSQLiteDatabase db) {
super.onOpen(db);
}
#Override
public void onDestructiveMigration(#NonNull SupportSQLiteDatabase db) {
super.onDestructiveMigration(db);
}
};
private static Migration Migration_1_2 = new Migration(1, 2) {
#Override
public void migrate(#NonNull SupportSQLiteDatabase database) {
}
};
private static boolean doesDatabaseExist(Context context) {
if (new File(context.getDatabasePath(DBNAME).getPath()).exists()) return true;
if (!(new File(context.getDatabasePath(DBNAME).getPath()).getParentFile()).exists()) {
new File(context.getDatabasePath(DBNAME).getPath()).getParentFile().mkdirs();
}
return false;
}
private static void copyFromAssets(Context context, boolean replaceExisting) {
boolean dbExists = doesDatabaseExist(context);
if (dbExists && !replaceExisting) return;
//First Copy
if (!replaceExisting) {
copyAssetFile(context);
setDBVersion(context,DBNAME,DatabaseConstants.DBVERSION);
return;
}
//Subsequent Copies
File originalDBPath = new File(context.getDatabasePath(DBNAME).getPath());
// Open and close the original DB so as to checkpoint the WAL file
SQLiteDatabase originalDB = SQLiteDatabase.openDatabase(originalDBPath.getPath(),null,SQLiteDatabase.OPEN_READWRITE);
originalDB.close();
//1. Rename original database
String preservedDBName = "preserved_" + DBNAME;
File preservedDBPath = new File (originalDBPath.getParentFile().getPath() + File.separator + preservedDBName);
(new File(context.getDatabasePath(DBNAME).getPath()))
.renameTo(preservedDBPath);
//2. Copy the replacement database from the assets folder
copyAssetFile(context);
//3. Open the newly copied database
SQLiteDatabase copiedDB = SQLiteDatabase.openDatabase(originalDBPath.getPath(),null,SQLiteDatabase.OPEN_READWRITE);
SQLiteDatabase preservedDB = SQLiteDatabase.openDatabase(preservedDBPath.getPath(),null,SQLiteDatabase.OPEN_READONLY);
//4. Apply preserved data to the newly copied data
copiedDB.beginTransaction();
for (TablePreserve tp: DatabaseConstants.TABLE_PRESERVELIST) {
preserveTableColumns(
preservedDB,
copiedDB,
tp.getTableName(),
tp.getPreserveColumns(),
tp.getExtractColumns(),
tp.getWhereColumns(),
true
);
}
copiedDB.setVersion(DatabaseConstants.DBVERSION);
copiedDB.setTransactionSuccessful();
copiedDB.endTransaction();
//5. Cleanup
copiedDB.close();
preservedDB.close();
preservedDBPath.delete();
}
private static void copyAssetFile(Context context) {
int buffer_size = 8192;
byte[] buffer = new byte[buffer_size];
int bytes_read = 0;
try {
InputStream fis = context.getAssets().open(DBNAME);
OutputStream os = new FileOutputStream(new File(context.getDatabasePath(DBNAME).getPath()));
while ((bytes_read = fis.read(buffer)) > 0) {
os.write(buffer,0,bytes_read);
}
os.flush();
os.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("Unable to copy from assets");
}
}
private static int getDBVersion(Context context, String databaseName) {
SQLiteDatabase db = SQLiteDatabase.openDatabase( context.getDatabasePath(databaseName).getPath(),null,SQLiteDatabase.OPEN_READONLY);
int rv = db.getVersion();
db.close();
return rv;
}
private static void setDBVersion(Context context, String databaseName, int version) {
SQLiteDatabase db = SQLiteDatabase.openDatabase( context.getDatabasePath(databaseName).getPath(),null,SQLiteDatabase.OPEN_READWRITE);
db.setVersion(version);
db.close();
}
private static boolean preserveTableColumns(
SQLiteDatabase originalDatabase,
SQLiteDatabase newDatabase,
String tableName,
String[] columnsToPreserve,
String[] columnsToExtract,
String[] whereClauseColumns,
boolean failWithException) {
StringBuilder sb = new StringBuilder();
Cursor csr = originalDatabase.query("sqlite_master",new String[]{"name"},"name=? AND type=?",new String[]{tableName,"table"},null,null,null);
if (!csr.moveToFirst()) {
sb.append("\n\tTable ").append(tableName).append(" not found in database ").append(originalDatabase.getPath());
}
csr = newDatabase.query("sqlite_master",new String[]{"name"},"name=? AND type=?",new String[]{tableName,"table"},null,null,null);
if (!csr.moveToFirst()) {
sb.append("\n\tTable ").append(tableName).append(" not found in database ").append(originalDatabase.getPath());
}
if (sb.length() > 0) {
if (failWithException) {
throw new RuntimeException("Both databases are required to have a table named " + tableName + sb.toString());
}
return false;
}
for (String pc: columnsToPreserve) {
boolean preserveColumnInExtractedColumn = false;
for (String ec: columnsToExtract) {
if (pc.equals(ec)) preserveColumnInExtractedColumn = true;
}
if (!preserveColumnInExtractedColumn) {
if (failWithException) {
StringBuilder sbpc = new StringBuilder().append("Column in Columns to Preserve not found in Columns to Extract. Cannot continuue." +
"\n\tColumns to Preserve are :-");
}
throw new RuntimeException("Column " + pc + " is not int the Columns to Extract.");
}
return false;
}
sb = new StringBuilder();
for (String c: whereClauseColumns) {
sb.append(c).append("=? ");
}
String[] whereargs = new String[whereClauseColumns.length];
csr = originalDatabase.query(tableName,columnsToExtract,sb.toString(),whereClauseColumns,null,null,null);
ContentValues cv = new ContentValues();
while (csr.moveToNext()) {
cv.clear();
for (String pc: columnsToPreserve) {
switch (csr.getType(csr.getColumnIndex(pc))) {
case Cursor.FIELD_TYPE_INTEGER:
cv.put(pc,csr.getLong(csr.getColumnIndex(pc)));
break;
case Cursor.FIELD_TYPE_STRING:
cv.put(pc,csr.getString(csr.getColumnIndex(pc)));
break;
case Cursor.FIELD_TYPE_FLOAT:
cv.put(pc,csr.getDouble(csr.getColumnIndex(pc)));
break;
case Cursor.FIELD_TYPE_BLOB:
cv.put(pc,csr.getBlob(csr.getColumnIndex(pc)));
}
}
int waix = 0;
for (String wa: whereClauseColumns) {
whereargs[waix] = csr.getString(csr.getColumnIndex(wa));
}
newDatabase.update(tableName,cv,sb.toString(),whereargs);
}
csr.close();
return true;
}
}
I debugged and modified the code by MikeT a bit, now heres the final kotlin library with an easy databaseBuilder
https://github.com/ueen/RoomAssetHelper
Please read the documentation and report if you encounter any issue, enjoy :)
I am trying to query data with Androids persistence library Room from a existing Database. No compilation error or runtime error. There is just no result in the cursor.
The query is checked and looks fine (also in the generated Java as mentioned in the answer of 'Pinakin' Android Room Database DAO debug log
The database existis in the /data/data/com.project/databases/ folder.
The database has the correct size but there are no other files such as mentioned in the answer of 'kosas' Android Room database file is empty
Similar style of copying as shown here https://android.jlelse.eu/room-persistence-library-with-pre-populated-database-5f17ef103d3d. I only changed the constructor to:
grammarDatabase = GrammarDatabase.getInstance(appContext);
because my GrammarDatabase looks like this:
#Database(
entities = {
Term.class
}, version = 1, exportSchema = false
)
public abstract class GrammarDatabase extends RoomDatabase {
private static final String DB_NAME = "database.db";
private static volatile GrammarDatabase instance;
public static synchronized GrammarDatabase getInstance(Context context) {
if (instance == null) {
instance = create(context);
}
return instance;
}
private static GrammarDatabase create(final Context context) {
return Room.databaseBuilder(context, GrammarDatabase.class, DB_NAME).fallbackToDestructiveMigration().allowMainThreadQueries().build();
}
public abstract TermDao getTermDao();
}
The .fallbackToDestructiveMigration() and .allowMainThreadQueries is just for testing purposes.
The interface 'TermDao_Impl' (query string shortened):
#Override
public Cursor getSearchContent(String searchTerm, String languageCode) {
final String _sql = "SELECT T._id, T.term FROM term AS T INNER JOIN ... INNER JOIN language AS L INNER JOIN ... AND L.code= ? AND T.term LIKE ?";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 2);
int _argIndex = 1;
if (languageCode == null) {
_statement.bindNull(_argIndex);
} else {
_statement.bindString(_argIndex, languageCode);
}
_argIndex = 2;
if (searchTerm == null) {
_statement.bindNull(_argIndex);
} else {
_statement.bindString(_argIndex, searchTerm);
}
final Cursor _tmpResult = __db.query(_statement);
return _tmpResult;
}
Call in MainActivity:
First: database = DatabaseHelper.getInstance(context).getGrammarDatabase();
Then:
Cursor c = database.getTermDao().getSearchContent("'"+searchTerm+"'", "'"+currentLanguageCode+"'");
if (c.moveToNext()) {
System.out.println("Found!");
System.out.println(c.getString(c.getColumnIndex("T.term")));
}
Tried to call with " ' " around the arguments and without.
So the cursor is empty eventhough the query will have a result (tested in DB Browser for SQLite). "Found!" Is not printed.
I have a parent entity Collection:
#Entity(tableName = "collections")
public class Collection implements Serializable {
#PrimaryKey
#NonNull
public String id;
public String collTitle;
public int isFavorite;
public int wordCount;
Collection(String collTitle, int isFavorite, int wordCount) {
this(UUID.randomUUID().toString(), collTitle, isFavorite, wordCount);
}
#Ignore
Collection(#NonNull String id, String collTitle, int isFavorite, int wordCount) {
this.id = id;
this.collTitle = collTitle;
this.isFavorite = isFavorite;
this.wordCount = wordCount;
}
}
and a child entity Word:
#Entity(tableName = "words",
foreignKeys = #ForeignKey(
entity = Collection.class,
parentColumns = "id",
childColumns = "collId",
onDelete = SET_DEFAULT),
indices = #Index("collId"))
public class Word implements Serializable {
#PrimaryKey
#NonNull
public final String id;
public String wordTitle;
public String collId;
public String pageNum;
public int isFavorite;
public String wordNotes;
#Ignore
Word(String wordTitle, String collId, String pageNum, int isFavorite, String wordNotes) {
this(UUID.randomUUID().toString(), wordTitle, collId, pageNum, isFavorite, wordNotes);
}
Word(#NonNull String id, String wordTitle, String collId, String pageNum, int isFavorite,
String wordNotes) {
this.id = id;
this.wordTitle = wordTitle;
this.collId = collId;
this.pageNum = pageNum;
this.isFavorite = isFavorite;
this.wordNotes = wordNotes;
}
}
At the time of inserting a new Collection, I give wordCount as 0 because every new collection would have 0 words associated with it. However, when inserting a new Word, I have to choose one collection from the defined collections and use its 'id' as 'collId' (Foreign Key).
Now I need to retrieve a list of Collection objects with 'wordCount' field containing the actual number of words associated with each Collection object. As already mentioned, 'wordCount' field is assigned a value of 0 at the time of insertion of a Collection object.
I just hope I made my question clear. Kindly suggest a query that serves my purpose. And I don't want to have the result in the form of a new POJO class like, for example, CollectionsAndWords.
Any help would be a great help!
OK, i solved it in the following way:
In my Collection entity, I annotated the wordCount as follows:
#ColumnInfo(name = "word_count")
public int wordCount;
and made changes to the constructors by removing this field:
Collection(String collTitle, int isFavorite) {
this(UUID.randomUUID().toString(), collTitle, isFavorite);
}
#Ignore
Collection(#NonNull String id, String collTitle, int isFavorite) {
this.id = id;
this.collTitle = collTitle;
this.isFavorite = isFavorite;
}
Finally, I added the following query method in my DAO:
#Query("SELECT collections.id, collections.collTitle, collections.isFavorite, " +
"COUNT(words.collId) as word_count " +
"FROM collections " +
"LEFT JOIN words " +
"ON collections.id = words.collId " +
"GROUP BY collections.id " +
"ORDER BY collections.collTitle")
List<Collection> findAllCollsWithCount();
The LEFT JOIN used in the right manner did all the trick for me.
Note that in above query, I'm not retrieving the wordCount field, I'm just letting a new field to be calculated and mapping it to wordCount. If I retrieve wordCount along with other columns from collections table, I get 02 columns that go by the name of word_count; one with 0 value and other with calculated value (COUNT()). I also tried to annotate wordCount with #Ignore but then Room couldn't find any column to map word_count to.
However, for now, the above solution is perfectly serving my purpose, so it is the answer. It might help someone with the same question.
Thanks!
What is the best way to save data read from SQLiteDatabase Android, and access them using row number or column name?
Hi Below is my code that I am using to fetch the data but I want to store the rows in some kind of dataset so that i can fetch the data using column number or name . I want to dynamically show these data in a grid in android.
MyDatabaseSQLHelper myDatabaseSQLHelper = new MyDatabaseSQLHelper(this);
SQLiteDatabase mySQLiteDatabase = myDatabaseSQLHelper.getReadableDatabase();
String[] projection = {
Items._ID,
Items.COL_ITEM_NAME,
Items.COL_ITEM_PRICE,
Items.COL_ITEM_QUANTITY,
Items.COL_ITEM_AMOUNT
};
Cursor cursor = mySQLiteDatabase.query(Items.TABLE_NAME,projection,null,null,null,null,null);
To store all rows fetch from database you can store it in Array list of type modal class. for you your modal class could be like this
ItemBeen.java
public class ItemBeen {
String id,itemName,itemPrice,itemQty,itemAmount;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public String getItemPrice() {
return itemPrice;
}
public void setItemPrice(String itemPrice) {
this.itemPrice = itemPrice;
}
public String getItemQty() {
return itemQty;
}
public void setItemQty(String itemQty) {
this.itemQty = itemQty;
}
public String getItemAmount() {
return itemAmount;
}
public void setItemAmount(String itemAmount) {
this.itemAmount = itemAmount;
}
}
then now in your class make method for get data from database and it store all record to array list and return that array list , like this.
public ArrayList<ItemBeen> getItemList() {
Cursor cur;
ArrayList<itemBeen> itemList = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
cur = db.query(Items.TABLE_NAME.TBL_ITEM, projection, null, null, null, null, null);
if (cur != null) {
if (cur.moveToFirst()) {
do {
ItemBeen bean = new ItemBeen();
bean.setId(cur.getString(cur.getColumnIndex(DBConstant.TBL_ITEM.KEY_ID)));
bean.setItemName(cur.getString(cur.getColumnIndex(DBConstant.TBL_ITEM.KEY_ITEM_NAME)));
bean.setItemPrice(cur.getBlob(cur.getColumnIndex(DBConstant.TBL_ITEM.KEY_ITEM_PRICE)));
bean.setItemQty(cur.getString(cur.getColumnIndex(DBConstant.TBL_ITEM.KEY_ITEM_TQY)));
bean.setItemAmount(cur.getInt(cur.getColumnIndex(DBConstant.TBL_ITEM.KEY_ITEM_AMT)));
itemList.add(bean);
} while (cur.moveToNext());
}
}
return itemList;
}
Now in that class in which you want all item list in that class call this method.declare array list and fetch data.
ArrayList<ItemBeen> itemList = new ArrayList<ItemBeen>();
itemList = DBConstant.dbHelper.getItemList();
and this way in itemList contains all records.
I want to update multiple cell of single row with active android.
some thing like this,
new Update(Question.class).set("UserAnswer ="+answerID,
"UserAnswerType = "+userAnswerType)
.where("QID = ?", questionID).execute();
but that will gives me error. is there any other way to do this? or am i missing something?
Here is my Question.class for reference
#Table(name = "Questions")
public class Question extends Model {
#Expose
#Column(name = "QID")
private Integer ID;
#Expose
#Column(name = "AnswerID")
private Integer AnswerID;
#Expose
#Column(name = "UserAnswer")
private Integer UserAnswer;
#Expose
#Column(name = "UserAnswerType")
private Integer UserAnswerType;
//Getter and setter methods for fields
public Question() {
// You have to call super in each constructor to create the table.
super();
}
}
You can do something like below. I have done it in one of my project and it worked fine.
String updateSet = " UserAnswer = ? ," +
" UserAnswerType = ? ";
new Update(Question.class)
.set(updateSet, answerID, userAnswerType)
.where(" QID = ? ", questionID)
.execute();
Following similar pattern for any number of columns will work. In case if you have Boolean type fields in your table, this issue can help you.
We Can achieve it like this way.
StringBuilder data=new StringBuilder();
if (!TextUtils.isEmpty(name.getText().toString()))
{
data.append("Name = '"+name.getText().toString()+"',");
}
if (!TextUtils.isEmpty(age.getText().toString()))
{
data.append("Age = '"+age.getText().toString()+"',");
}
if (!TextUtils.isEmpty(empCode.getText().toString()))
{
data.append("EmployeeCode = '"+empCode.getText().toString()+"',");
}
if (!TextUtils.isEmpty(mobNum.getText().toString()))
{
data.append("MobileNumber = '"+mobNum.getText().toString()+"',");
}
if (!TextUtils.isEmpty(adress.getText().toString()))
{
data.append("address = '"+adress.getText().toString()+"'");
}
String str=data.toString();
//-------------and update query like this-----------------
new Update(EmpProfile.class).set(str).where("EmployeeCode = ?", empCode)
.execute();
String x="sent = 1,file = "+n.getString("file");
new Update(Messages.class)
.set(x)
.where("id = ?", lid)
.execute();