Room - Query all memory handling - android

I'm considering using the android Room library as a ORM in my app, but i would like to know more details/comments because of some constraints i have and i was not able to find on the internet (ie.Google;))
When i do the following query:
#Query("SELECT * FROM users")
List<User> getAll();
and if i have thousands off users, wouldn't it be an issue? because from the generated code below it seems to load everything into an ArrayList. Event the LiveData> or Flowable> do the same.
#Override
public List<User> getAll() {
final String _sql = "SELECT * FROM Users";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
final Cursor _cursor = __db.query(_statement);
try {
final int _cursorId = _cursor.getColumnIndexOrThrow("id");
final int _cursorName = _cursor.getColumnIndexOrThrow("name");
final List<User> _result = new ArrayList<User>(_cursor.getCount());
while(_cursor.moveToNext()) {
final User _item;
final String _tmpMId;
_tmpMId = _cursor.getString(_cursorId);
final String _tmpMName;
_tmpMName = _cursor.getString(_cursorName);
_item = new User(_tmpMId,_tmpMName);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
_statement.release();
}
}
#Override
public Flowable<List<User>> getAllRX() {
final String _sql = "SELECT * FROM Users";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
return RxRoom.createFlowable(__db, new String[]{"Users"}, new Callable<List<CachedAttendee>>() {
public List<User> call() throws Exception {
final Cursor _cursor = __db.query(_statement);
try {
final int _cursorId = _cursor.getColumnIndexOrThrow("id");
final int _cursorName = _cursor.getColumnIndexOrThrow("name");
final List<User> _result = new ArrayList<User>(_cursor.getCount());
while(_cursor.moveToNext()) {
final User _item;
final String _tmpMId;
_tmpMId = _cursor.getString(_cursorId);
final String _tmpMName;
_tmpMName = _cursor.getBlob(_cursorName);
_item = new User(_tmpMId,_tmpMName);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
}
}
#Override
protected void finalize() {
_statement.release();
}
});
}
Am i looking at it wrongly or Google dismissed this point? I can always use Cursors, but that defeats the point of having an ORM handling that serialisation for me.
Cheers,

and if i have thousands off users, wouldn't it be an issue? because from the generated code below it seems to load everything into an ArrayList.
You asked it to do that. If you do not want a List of all users, do not ask for it. Create a #Query that uses some sort of constraints (e.g., WHERE, LIMIT/OFFSET).
This is not significantly different from other ORM solutions. That being said, if you find some other ORM that you like better, use it. Room is an option, not a requirement.

You can consider pagination to improve this problem.
Query is :
#Query("SELECT * FROM user LIMIT :limit OFFSET :offset")
User[] loadAllUsersByPage(int limit,int offset);
Here, it will give a list of user based on limit and offset.
if loadAllUsersByPage(2,0) it will return first 2 rows from table.
if loadAllUsersByPage(2,1) it will return 3rd and 4th rows from table.
but if loadAllUsersByPage(-1,10) then it will serve first 10 rows from table.
Thanks :)

Related

Realm: updateOrInsert without index

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.

Android Room update query getting stuck on auto generated code

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.

Update on Content Provider does nothing

I'm trying to make an Android app to help people suffering from headaches. I have a sqlite database to store the crisis, and users can add a crisis by pushing a button. The same button is used to indicate the crisis is over. In other words, when you feel the headache coming, you push the button ; then, when it's over, you press it again and the application updates the corresponding entry whith the "end date".
But if my insert does well, my update does not update at all. Here is how it is supposed to work :
I first retrieve the latest entry in my database (which is the one with the greatest id), then I get the actual date, and put it in a ContentValue. Finally I update the entry.
Here is the button code :
public void onClickStartStop(View v){
Log.v("andromed", "Starting/Stopping crisis");
String d = new Date().toString();
ContentValues cv = new ContentValues();
String user_info = "";
String[] projection = {CriseContract.COLUMN_NAME_CRISE_ID, CriseContract.COLUMN_NAME_CRISE_DEBUT,CriseContract.COLUMN_NAME_CRISE_FIN};
Cursor criseCursor = getContentResolver().query(CriseContract.CONTENT_URI, projection,"SELECT MAX("+CriseContract.COLUMN_NAME_CRISE_ID+") FROM "+CriseContract.TABLE_NAME, null, null);
Log.v("andromed",""+criseCursor.getCount());
if(criseCursor.getCount()>=0){
while(criseCursor.moveToNext()){
String date_fin = criseCursor.getString(criseCursor.getColumnIndex(CriseContract.COLUMN_NAME_CRISE_FIN));
if(!(date_fin==(null))){
Log.v("andromed","Date exists "+date_fin);
user_info = "Crise enregistrée";
cv.put(CriseContract.COLUMN_NAME_CRISE_DEBUT, d);
Uri u = getContentResolver().insert(CriseContract.CONTENT_URI, cv);
}else{
String date_deb = criseCursor.getString(criseCursor.getColumnIndex(CriseContract.COLUMN_NAME_CRISE_DEBUT));
if(date_deb==null){
Log.v("andromed","No date in db");
user_info = "Crise enregistrée";
cv.put(CriseContract.COLUMN_NAME_CRISE_DEBUT, d);
Uri u = getContentResolver().insert(CriseContract.CONTENT_URI, cv);
}else{
Log.v("andromed", "Need to close the crisis");
cv.put(CriseContract.COLUMN_NAME_CRISE_FIN, d);
int tmp = getMaxId();
String where = CriseContract.COLUMN_NAME_CRISE_ID+"="+tmp;
String[] st = {""+tmp};
int nup = getContentResolver().update(CriseContract.CONTENT_URI,cv, where, null);
Log.v("andromed", nup+" rows updated");
user_info = "Crise terminée";
}
}
}
}else{
user_info = "Erreur lors de la lecture";
}
Toast t = Toast.makeText(getApplicationContext(),user_info, Toast.LENGTH_LONG);
t.show();
}
(Don't mind the Log and the toast stuff, just for me).
Here is my function to retrieve the maximum id :
private int getMaxId(){
String[] projection = {CriseContract.COLUMN_NAME_CRISE_ID};
String selection = "SELECT MAX("+CriseContract.COLUMN_NAME_CRISE_ID+") FROM "+CriseContract.TABLE_NAME;
Cursor c = getContentResolver().query(CriseContract.CONTENT_URI, projection, selection, null, null);
Log.v("andromed", ""+c.getCount());
int maxid=-1;
if(c!=null){
while(c.moveToNext()){
maxid = c.getInt(c.getColumnIndex(CriseContract.COLUMN_NAME_CRISE_ID));
}
}
Log.v("andromed", "Greatest id in table Crise : "+maxid);
return maxid;
}
And of course, my contract class :
public final static class CriseContract{
public static final String AUTHORITY = "com.piertris.andromed";
public static final String BASE_PATH = "database";
public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+BASE_PATH);
public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE+"/"+BASE_PATH;
public static final String CONTENT_ITEM_TYPE= ContentResolver.CURSOR_ITEM_BASE_TYPE+"/andromed";
public static final String TABLE_NAME = "crises";
public static final String COLUMN_NAME_CRISE_ID = "criseid";
public static final String COLUMN_NAME_CRISE_DEBUT = "date_debut";
public static final String COLUMN_NAME_CRISE_FIN = "date_fin";
public static final String COLUMN_NAME_INTENSITE = "intensite";
public static final String COLUMN_NAME_SYMPTOM = "symptome";
public static final String COLUMN_NAME_MED = "prise_med";
public static final String COLUMN_NAME_MEDS = "type_med";
public static final String COLUMN_NAME_AURA = "aura";
public static final String COLUMN_NAME_COMMENT = "comments";
}
When I try to end the current crisis, my Logcat tells me that 0 rows were updated.
Thanks to SO, I already corrected other problems due to a wrong use of the function, but this time, the only link I found was this one : Android content provider not updating database and the OP just added a comment saying he updated his ContentProvider, but nothing more.
What am I doing wrong ? Did I "misnamed" my column names ? Do I misuse the update function ?
Thanks for your help.
EDIT
Thanks to Jozua, I realized that I didn't implement the update function in my ContentProvider file. Alright, I feel extremely dumb right now. I'll keep you informed on how does it work once the update() function is written.
Once again, thanks Jozua.
Alright, I kind of solved the problem, but in a really bad way.
Considering the fact that I retrieve the crisis just once in order to add almost everything that is needed but the beginning date and the id, I simply turned my update() request into a delete() followed by an update() in which I pass a ContentValue containing the values of the row I previously deleted.
I know it is really bad programming, but at least it works.
I won't accept my answer, in case someone find out what was wrong with my update() function and could possibly help someone else (and even me, so that I can improve my code).
That's it :)
Here is the portion of relevant code :
public void onClickStartStop(View v){
//go straight to relevant part
String date_deb = criseCursor.getString(criseCursor.getColumnIndex(CriseContract.COLUMN_NAME_CRISE_DEBUT));
Log.v("andromed", "Need to close the crisis");
cv.put(CriseContract.COLUMN_NAME_CRISE_ID, criseCursor.getInt(criseCursor.getColumnIndex(CriseContract.COLUMN_NAME_CRISE_ID)));
cv.put(CriseContract.COLUMN_NAME_CRISE_DEBUT, date_deb);
cv.put(CriseContract.COLUMN_NAME_CRISE_FIN, d);
int ndel = getContentResolver().delete(CriseContract.CONTENT_URI, CriseContract.COLUMN_NAME_CRISE_ID+"=?", new String[] {""+criseCursor.getInt(criseCursor.getColumnIndex(CriseContract.COLUMN_NAME_CRISE_ID))});
Log.v("andromed", ndel+" rows deleted");
Uri u = getContentResolver().insert(CriseContract.CONTENT_URI, cv);
user_info = "Crise terminée";
//End of relevant code
}
Thanks to those who might have searched anyway.

Android Ormlite upgrade issue with ForeignCollectionField

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

ORMLite Select Distinct Fields

I have a SQLite table (on Android) that has numerous fields, but certain fields are repeated/denormalized. I would like to select a distinct set of this data and use them as actual objects.
Example
books table
title summary author
Little Johnny A funny kid Johnny Himself
Big Johnny A funny adult Johnny Himself
I would like to extract one author from this list ("Johnny Himself") and would expect I should be able to do this with ORMLite instead of manually with Java.
I would like to select a distinct set of this data and use them as actual objects.
ORMLite supports a distinct() method on the QueryBuilder that should do what you want. So your code would look something like:
List<Book> results = booksDao.queryBuilder()
.distinct().selectColumns("author").query();
In this case, the resulting Book objects would only have the author field set and not the id field or anything else. If you just wanted the author names instead of objects then you could do:
GenericRawResults<String[]> rawResults =
booksDao.queryRaw("SELECT DISTINCT author FROM books");
for (String[] resultColumns : rawResults) {
String author = resultColumns[0];
...
}
This is my application code
public class DbHelper<T> {
private Class<T> c;
private DatabaseHelper db;
public DbHelper(Class<T> c) {
this.c = c;
db = DatabaseHelper.getInstance();
}
This is a good idea
public List<T> queryForBuilderDistinct(int offset, int limit, String ColumnsName,
String orderName, boolean isAsc) {
try {
Dao<T, Integer> dao = db.getDao(c);
QueryBuilder<T, Integer> queryBuilder = dao.queryBuilder();
if (offset != 0) {
queryBuilder.offset((long) offset);
}
if (limit != 0) {
queryBuilder.limit((long) limit);
}
if (orderName != null) {
queryBuilder.orderBy(orderName, isAsc);
}
queryBuilder.distinct().selectColumns(ColumnsName);
return dao.query(queryBuilder.prepare());
} catch (SQLException e) {
LogUtil.e(TAG, "queryForBuilderDistinct", e);
}
return new ArrayList<T>();
}

Categories

Resources