Java - Realm Migration (v 0.85.0) - Field count does not match - android

I am attempting my first Realm migration in one of my projects using Realm v 0.85.0. I am migrating from v 0.84.0 but the migration should also work for earlier versions. I have followed the example at https://github.com/realm/realm-java/tree/master/examples/migrationExample/src/main that was linked to in the documentation.
In this migration I am attempting to add two new tables. In order to add each new table my migration code looks like the following:
public class Migration implements RealmMigration {
#Override
public long execute(Realm realm, long version) {
if (version == 0)
{
Table newTableOne = realm.getTable(NewTableOne.class);
newTableOne.addColumn(ColumnType.STRING, "columnOne");
// Add any other needed columns here and repeat process for NewTableTwo
// Rest of migration logic goes here...
version++;
}
return version;
}
}
According to Migration on Realm 0.81.1 the getTable() method will automatically create the table if it does not exist. I do not think this is the problem but I've included it just for completeness.
I am also attempting to add a couple of new columns to an existing table and set default values on these new columns. To do this I am using the following code:
Table existingTable = realm.getTable(ExistingTable.class);
existingTable.addColumn(ColumnType.BOOLEAN, "newColumnOne");
existingTable.addColumn(ColumnType.INTEGER, "newColumnTwo");
// Any other new columns needed here
long newColumnOneIndex = getIndexForProperty(existingTable, "newColumnOne");
long newColumnTwoIndex = getIndexForProperty(existingTable, "newColumnTwo");
for (int i = 0; i < existingTable.size(); i++)
{
userTable.setBoolean(newColumnOneIndex, i, false);
userTable.setLong(newColumnTwoIndex, i, 5);
}
The getIndexForProperty method is pulled directly from the example on Github and looks like:
private long getIndexForProperty(Table table, String name) {
for (int i = 0; i < table.getColumnCount(); i++) {
if (table.getColumnName(i).equals(name)) {
return i;
}
}
return -1;
}
When this migration is run I am getting a RealmMigrationNeededException that states "Field count does not match - expected 22 but was 23". I have looked around StackOverflow and done some research via Google and on the Github wiki but have not been able to find any information relating to this exact "Field count does not match" message.
I have ensured that there is an addColumn line for each new field in each model class and there are not more fields in my model than I am adding columns and vice versa.
Any help that you may be able to provide would be greatly appreciated.

Related

Room DB - Running insertion into M2M Relation inside Transaction

I am inserting a new book into my book table and after trying to assign it to a many-to-many relation table. Imo this should run in a transaction.
(Because if the m2m insertion fails, the information about the realtionship is lost). My code now looks as follows and fails as i cannot access the BookUserXRefDao.insert(bookUser); query due to static context errors.
Is there an easy way to fix this?
#Transaction
public void insertBook(Book theBook, List<Integer> userIds){
long newBookId= insert(theBook);
//Insert into the m2m relation
BookUserXRef[] bookUser = new BookUserXRef[userIds.size()];
for (int i = 0; i < userIds.size(); i++) {
BookUserXRef[i] = new BookUserXRef(newBookId,userIds.get(i));
}
BookUserXRefDao.insert(bookUser);
}
Just realized that i can access the Singleton Database Instance from within my transaction.
Therefore i could just use
AppDb.getAppDb().BookUserXRefDao().insert(bookUser);
That solved the problem.

GreenDAO does not persist data across application restart

I want to use GreenDAO for persistence, but I cannot get it to persist my data.
The data is saved and loaded correctly as long as the application is not restarted.
Once i swipe the app away and reopen it from scratch, GreenDAO does not see the previous data (both on the emulator and real device).
This is my entity:
#Entity
public class TestSingleEntity {
#Id(autoincrement = true)
Long id;
int someNumber;
public TestSingleEntity(int someNumber) {
this.someNumber = someNumber;
}
#Generated(hash = 787203968)
public TestSingleEntity(Long id, int someNumber) {
this.id = id;
this.someNumber = someNumber;
}
#Generated(hash = 1371368161)
public TestSingleEntity() {
}
// ... some more stuff
}
This is how I insert entities to database:
Random rnd = new Random();
TestSingleEntity singleEntity = new TestSingleEntity();
singleEntity.setSomeNumber(rnd.nextInt());
DaoSession session = ((MyApp)getApplication()).getDaoSession();
TestSingleEntityDao dao = session.getTestSingleEntityDao();
dao.insert(singleEntity);
Log.d("tgd", "Inserted an entity with id " + singleEntity.getId());
And this is how I read them:
Query query = dao.queryBuilder().orderAsc(TestSingleEntityDao.Properties.SomeNumber).build();
StringBuilder builder = new StringBuilder();
List<TestSingleEntity> result = query.list();
Log.d("size", result.size());
for (TestSingleEntity testSingleEntity : result) {
Log.d("entity", testSingleEntity.toString());
}
As I have said, as long as I stay in the app (moving around in different activities is okay), everytime the insert is called, a new entity with a new ID is created. As soon as I relaunch the app, it goes back to square one.
The setup was taken directly from the GitHub page. What am I doing wrong? Thanks
Disclaimer: GreenDAO has gone through major changes since I last used it so this is purely based on reading their code on the github.
Apparently GreenDAO's poorly documented DevOpenHelper drops all tables on upgrade, so the real question is why is onUpgrade being called when clearly there hasn't been a change to the schema version. Try to look for the log line that mentions dropping the tables as described in the template for DevOpenHelper.
Regardless, using OpenHelper instead should fix the issue.

Realm on Android - How to select multiple objects by list of ids (#PrimaryKey)?

I'm building an Android app with the Realm database.
I have a RealmObject subclass called Article which has an id field (it's and int and also a #PrimaryKey). I would like to pass to a query a list of ints (a Set, int[], or whatever) of article id's and retrieve only those articles.
In SQL would be like this:
SELECT *
FROM `table`
where ID in (5263, 5625, 5628, 5621)
I've seen it's possible to do this in iOS in this StackOverflow question.
How can I do this in Android? Thanks!
Edit: Just to inform, I also asked this on the GitHub repo here.
Update:
Realm 1.2.0 has added RealmQuery.in() for a comparison against multiple values. The documentation details all the available overloads. This one is the method we can use if our ids are Integers:
public RealmQuery<E> in(String fieldName, Integer[] values)
Original answer:
The answer from #ChristianMelchior returns all articles if the list of ids is empty. I want it to return an empty RealmResults<Article>. That's what I've ended up doing:
Set<Integer> articleIds = this.getArticleIds();
RealmQuery<Article> query = realm.where(Article.class);
if (articleIds.size() == 0) {
// We want to return an empty list if the list of ids is empty.
// Just use alwaysFalse
query = query.alwaysFalse();
} else {
int i = 0;
for (int id : articleIds) {
// The or() operator requires left hand and right hand elements.
// If articleIds had only one element then it would crash with
// "Missing right-hand side of OR"
if (i++ > 0) {
query = query.or();
}
query = query.equalTo("id", id);
}
}
return query.findAll();
Now realm v 1.2.0 support RealmQuery.in() for a comparison against multiple values.
The Realm Java API's doesn't support this yet unfortunately. You can follow the feature request here https://github.com/realm/realm-java/issues/841
The current work-around would be to build up the query yourself in a for-loop:
RealmResults<Article> articles = realm.allObjects(Article.class);
RealmQuery q = articles.where();
for (int id : ids) {
q = q.equalTo("id", id);
}
RealmResults<Article> filteredArticles = q.findAll();
This is the way Realm does it since 1.2.0:
public RealmQuery<E> in(String fieldName, String[] values) {
if (values == null || values.length == 0) {
throw new IllegalArgumentException(EMPTY_VALUES);
}
beginGroup().equalTo(fieldName, values[0]);
for (int i = 1; i < values.length; i++) {
or().equalTo(fieldName, values[i]);
}
return endGroup();
}
Previously this is how I did it
I just came across this post and I thought I could throw in my 2 cents on this. As much as I appreciate Christian Melchior and his answers I think in this case his answer is not working (at least in the current version).
I prefer to do it like this - I personally think it's more readable than Albert Vila's answer:
List<String> listOfIds = [..];
RealmQuery<SomeClass> query = realm.where(SomeClass.class);
boolean first = true;
for (String id : listOfIds) {
if (!first) {
query.or();
} else {
first = false;
}
query.equalTo("id", id);
}
RealmResults<SomeClass> results = query.findAll();

How to add 1 milion items in Realm correctly?

I want to choose between native SQLiteDatabase and Realm to deal with a big amount of data.
For benchmark I add to storage 1 milion of Product entities:
{id:integer,sku:string,name:string,data_creating:string}
Using SQLiteDatabase it takes near 1 minute 34 seconds on my device.
Using Realm it takes more them 10 minutes.
My code is:
Realm realm = Realm.getInstance(getApplicationContext());
realm.beginTransaction();
for(int i = 0 ; i < 1000000;i++){
Product product = realm.createObject(Product.class);
product.setId(i+1);
product.setName("Product_"+i);
product.setSku("SKU__"+i);
product.setDateCreated(new Date());
}
realm.commitTransaction();
How can I improve my code for better time performance?
The original question spawned a discussion within Realm, and we ended up adding a faster method to insert objects. The code for creating and inserting 1 mio objects can now be written as:
final Product product = new Product();
final Date date = new Date();
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
for(int i = 0 ; i < 1000000; i++){
product.setId(i+1);
product.setName("Product_"+i);
product.setSku("SKU__"+i);
product.setDateCreated(date);
realm.insert(product);
}
}
});
}
You have to be aware that SQLite and Realm are two very different things. Realm is an object store and you are creating a lot of objects in the code shown above. Depending on your model class and the number of rows/objects, you will often see that Realm is a bit slower on inserts. To do a fair comparison, you could compare Realm with one of the many excellent ORMs out there.
Said that, Realm offers a low-level interface (io.realm.internal). I wouldn't recommend you to use it as it is currently undocumented. Your example would look like this:
long numberOfObjects = 1000000;
SharedGroup sharedGroup = new SharedGroup("default.realm");
WriteTransaction writeTransaction = sharedGroup.beginWrite();
Table table = writeTransaction.getTable("class_Product");
table.addEmptyRows(numberOfObjects);
for (int i = 0; i < numberOfObjects; i++) {
table.setLong(0, i, i); // id
table.setString(1, i, "Product_"+i); // name
table.setString(2, i, "SKU__"+i); // sku
table.SetDate(3, i, new Date()); // date
}
writeTransaction.commit();
sharedGroup.close();
You can now compare two table/row oriented data stores, and you will probably find that Realm is a bit faster than SQLite.
At Realm, we have a few ideas on how to get our object interface to run faster, and we hope to be able to implement them in the near future.

Robolectric: Testing with ormlite

I'm trying to test ORMLite DAOs with robolectric, but database behaviour is not the same as when it's used from my android app. My DAOs are working perfectly well on the android application.
Reading about robolectric shadows and debugging code, I encountered ShadowSQLiteOpenHelper (code here).
Does anyone know if this Shadow is enough to test ormlite daos? Or I have to create my own shadow to achieve that? Any clue/tip/suggestion/example here?
Thanks in advance.
Extra info:
Test method:
#Test
public void basicTest() throws SQLException {
assertNotNull(randomStringResource); // Injection of an android resource: OK
assertThat(randomStringResource, equalTo("Event")); // With correct value: OK
assertNotNull(eventDao); // Dao injection: OK
assertThat(eventDao.countOf(), equalTo(0L)); // Table empty: OK
Event e1 = new Event("e1", new Date());
eventDao.create(e1);
assertNotNull(e1.getId()); // ID generated by OrmLite: OK
assertThat(eventDao.countOf(), equalTo(1L)); // Table not empty: OK
assertThat("e1", equalTo(eventDao.queryForId(e1.getId()).getName())); // Query for inserted event: Throws exception
}
Some of the problems encountered running this test:
Errors querying entities with "camelCased" property names: error thrown at last line of test (related problem). Never had a problem like this running the android app.
When I changed one of these properties name (e.g., isEnabled to enabled) in order to avoid the camelCase problem, the previous error persisted... seems like memory database didn't apply the changes that I made on the entity.
Versions used:
Robolectric 1.1
OrmLite 4.41
Sorry for resurrecting your topic but I ran into the same problem.
I'm using OrmLite 4.45 and Robolectric 2.1.
In ShadowSQLiteCursor.java, cacheColumnNames method calls toLowerCase on each column name. So I decided to extend ShadowSQLiteCursor with my own (which doesn't call toLowerCase):
/**
* Simulates an Android Cursor object, by wrapping a JDBC ResultSet.
*/
#Implements(value = SQLiteCursor.class, inheritImplementationMethods = true)
public class ShadowCaseSensitiveSQLiteCursor extends ShadowSQLiteCursor {
private ResultSet resultSet;
public void __constructor__(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {}
/**
* Stores the column names so they are retrievable after the resultSet has closed
*/
private void cacheColumnNames(ResultSet rs) {
try {
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
columnNameArray = new String[columnCount];
for(int columnIndex = 1; columnIndex <= columnCount; columnIndex++) {
String cName = metaData.getColumnName(columnIndex);
this.columnNames.put(cName, columnIndex - 1);
this.columnNameArray[columnIndex - 1] = cName;
}
} catch(SQLException e) {
throw new RuntimeException("SQL exception in cacheColumnNames", e);
}
}
}
My answer obviously comes too late but may help others!

Categories

Resources