Android Realm Upgrade guide? [duplicate] - android

I'm currently running Realm Version 0.82.0 in one of my Android projects. I didn't touch Realm for quite some time, until I recently noticed that they went up until version 2.0.2 in the meantime. I would like to upgrade my version of Realm, unfortunately, I don't know if the upgrade from my old version to the current release will be working or breaking my code.
I'm especially concerned of migrations, since the API for migrations seemed to have changed a bit since my code, and I'm unsure if my migrations will break if I just update my version. Unfortunately, there is no documentation about upgrading Realm version available on their webpage.
Does anyone have any experience with upgrading Realm, especiall a version increase over two major versions?

The list of breaking changes is available in the CHANGELOG.MD on their Github.
However, it's worth noting that there were quite a few breaking changes on the road, especially noting 0.89.0.
From 0.82.0 to 5.1.0 is the following (which is the most stable version at the moment):
0.82.0:
BREAKING CHANGE: Fields with annotation #PrimaryKey are indexed automatically now. Older schemas require a migration.
(0.82.2 was most stable here, but it didn't work on Blackberry devices. The first stable version to use on Blackberry was 0.87.2.)
In 0.86.0+, you can add an index to the annotated field using
#Override
public void migrate(final DynamicRealm realm, long oldVersion, long newVersion) {
RealmSchema schema = realm.getSchema();
// version check and stuff
RealmObjectSchema personSchema = schema.get("Person");
personSchema.addIndex("fieldName");
0.83:
BREAKING CHANGE: Database file format update. The Realm file created by this version cannot be used by previous versions of Realm.
BREAKING CHANGE: Removed deprecated methods and constructors from the Realm class.
BREAKING CHANGE: Introduced boxed types Boolean, Byte, Short, Integer, Long, Float and Double. Added null support. Introduced annotation #Required to indicate a field is not nullable. String, Date and byte[] became nullable by default which means a RealmMigrationNeededException will be thrown if an previous version of a Realm file is opened.
Oh boy, this is a nice one. NULL support.
Boxed types for primitives became available. Boxed types are nullable by default. All String, Date, and byte[] must be annotated with #Required, or schema.setNullable("fieldName", nullability) and make them all nullable.
0.84.0:
Async queries were added. Nothing new here in terms of schema.
0.85.0:
BREAKING CHANGE: Removed RealmEncryptionNotSupportedException since the encryption implementation changed in Realm's underlying storage engine. Encryption is now supported on all devices.
BREAKING CHANGE: Realm.executeTransaction() now directly throws any RuntimeException instead of wrapping it in a RealmException (#1682).
BREAKING CHANGE: RealmQuery.isNull() and RealmQuery.isNotNull() now throw IllegalArgumentException instead of RealmError if the fieldname is a linked field and the last element is a link (#1693).
Nothing important here yet, although:
Setters in managed object for RealmObject and RealmList now throw IllegalArgumentException if the value contains an invalid (unmanaged, removed, closed, from different Realm) object (#1749).
This one is an interesting one. Previously it just failed, so this is for the best. But it is Realm's largest limitation, too.
0.86.0:
BREAKING CHANGE: The Migration API has been replaced with a new API.
BREAKING CHANGE: RealmResults.SORT_ORDER_ASCENDING and RealmResults.SORT_ORDER_DESCENDING constants have been replaced by Sort.ASCENDING and Sort.DESCENDING enums.
BREAKING CHANGE: RealmQuery.CASE_SENSITIVE and RealmQuery.CASE_INSENSITIVE constants have been replaced by Case.SENSITIVE and Case.INSENSITIVE enums.
BREAKING CHANGE: Realm.addChangeListener, RealmObject.addChangeListener and RealmResults.addChangeListener hold a strong reference to the listener, you should unregister the listener to avoid memory leaks.
BREAKING CHANGE: Removed deprecated methods RealmQuery.minimum{Int,Float,Double}, RealmQuery.maximum{Int,Float,Double}, RealmQuery.sum{Int,Float,Double} and RealmQuery.average{Int,Float,Double}. Use RealmQuery.min(), RealmQuery.max(), RealmQuery.sum() and RealmQuery.average() instead.
BREAKING CHANGE: Removed RealmConfiguration.getSchemaMediator() which is public by mistake. And RealmConfiguration.getRealmObjectClasses() is added as an alternative in order to obtain the set of model classes (#1797).
BREAKING CHANGE: Realm.addChangeListener, RealmObject.addChangeListener and RealmResults.addChangeListener will throw an IllegalStateException when invoked on a non-Looper thread. This is to prevent registering listeners that will not be invoked.
Added new Dynamic API using DynamicRealm and DynamicRealmObject.
Added Realm.getSchema() and DynamicRealm.getSchema().
The new Migration API, using DynamicRealm instead of Realm.getTable().
Some stuff were renamed, and you ought to unregister your change listeners if your result set is still valid. But it's worth noting that you should still retain a field variable to your RealmResults, because Realm's Context only has a weak reference to it.
0.87.0:
RX support. Nothing important.
0.87.2:
Removed explicit GC call when committing a transaction (#1925).
Finally, Realm got stable again! :)
0.88.0:
Breaking changes
Realm has now to be installed as a Gradle plugin.
DynamicRealm.executeTransaction() now directly throws any RuntimeException instead of wrapping it in a RealmException (#1682).
DynamicRealm.executeTransaction() now throws IllegalArgumentException instead of silently accepting a null Transaction object.
String setters now throw IllegalArgumentException instead of RealmError for invalid surrogates.
DynamicRealm.distinct()/distinctAsync() and Realm.distinct()/distinctAsync() now throw IllegalArgumentException instead of UnsupportedOperationException for invalid type or unindexed field.
All thread local change listeners are now delayed until the next Looper event instead of being triggered when committing.
Removed RealmConfiguration.getSchemaMediator() from public API which was deprecated in 0.86.0. Please use RealmConfiguration.getRealmObjectClasses() to obtain the set of model classes (#1797).
Realm.migrateRealm() throws a FileNotFoundException if the Realm file doesn't exist.
It is now required to unsubscribe from all Realm RxJava observables in order to fully close the Realm (#2357).
Welp. It's an AAR now. You have to add to classpath and run it with apply plugin: 'realm-android' instead of compile ... dependency.
Change listeners are only called on the next event loop, instead of immediately after commit. I'm.. honestly not entirely sure of the ramifications of this, but it means change listeners don't work on background threads. Only on looper threads (primarily the UI thread).
Enhancements
Support for custom methods, custom logic in accessors, custom accessor names, interface implementation and public fields in Realm objects (#909).
Improved .so loading by using ReLinker.
This is quite necessary though, so I wouldn't want to get stuck on 0.87.5 for sure.
0.89.0:
Breaking changes
#PrimaryKey field value can now be null for String, Byte, Short, Integer, and Long types. Older Realms should be migrated, using RealmObjectSchema.setNullable(), or by adding the #Required annotation. (#2515).
RealmResults.clear() now throws UnsupportedOperationException. Use RealmResults.deleteAllFromRealm() instead.
RealmResults.remove(int) now throws UnsupportedOperationException. Use RealmResults.deleteFromRealm(int) instead.
RealmResults.sort() and RealmList.sort() now return the sorted result instead of sorting in-place.
RealmList.first() and RealmList.last() now throw ArrayIndexOutOfBoundsException if RealmList is empty.
Removed deprecated method Realm.getTable() from public API.
Realm.refresh() and DynamicRealm.refresh() on a Looper no longer have any effect. RealmObject and RealmResults are always updated on the next event loop.
Okay, this one is the most messy one.
1.) you must add #Required annotation for #PrimaryKey annotated fields, because null is a valid primary key value.
2.) realm.refresh() no longer works. It will be removed anyways. Here's a workaround for 1.1.1 though, should be used only on background threads. It is available in Realm 3.2 again, though.
3.) getTable() is removed. Don't use it. Use the new migration API.
4.) realmResults.sort() returns a new RealmResults, which needs to have the change listener appended to it as well. I think it's unreliable, so I'd just use findAllSorted() instead.
5.) You might not think much of it, but
RealmObject and RealmResults are always updated on the next event loop. (NOTE: THIS IS NO LONGER TRUE SINCE REALM 3.1+ WHERE CHANGE LISTENERS ARE CALLED AGAIN ON commitTransaction())
This literally meant that RealmResults were only updated when the event loop occured, it was NOT immediately updated when you call realm.commitTransaction(). This also means that on background threads, the RealmResults did NOT update when you commitTransaction(), you had to requery them.
The RealmResults are only known to be updated after the appended RealmChangeListener is called. In 1.1.1, when the RealmChangeListener is called, all Results had been updated.
This change however also changed iteration behavior in transactions. In transactions, you always saw the newest version. This meant that a query was re-evaluated as you were iterating on it, and modifying elements. (THIS IS ALSO THE CASE SINCE REALM 3.0)
Example, previously this was valid code:
RealmResults<Stuff> stuffs = realm.where(Stuff.class).equalTo("something", false).findAll();
while(!stuffs.isEmpty()) {
stuffs.get(0).setSomething(true);
}
// you end up here because stuffs will be empty
// because of live auto-updates in transactions
However, this will no longer work. For me, this caused issues because I iterated sometimes like this
RealmResults<Stuff> stuffs = realm.where(Stuff.class).equalTo("something", false).findAll();
for(int i = 0; i < stuffs.size(); i++) {
stuffs.get(i--).setSomething(true);
}
// I end up here because of live auto-updates
This is a problem, because the stuffs will no longer change. I had to do a search for -- in my code and fix all iteration like this.
The official workaround used to be this:
RealmResults<Stuff> stuffs = realm.where(Stuff.class).equalTo("something", false).findAll();
for(int i = stuffs.size()-1; i >= 0; i--) {
stuffs.get(i).setSomething(true);
}
// I end up here because of normal iteration
This would still work fine in 0.89.0.
Since 0.89.0, this is valid code too (and in 3.0.0+, this automatically creates a snapshot collection):
RealmResults<Stuff> stuffs = realm.where(Stuff.class).equalTo("something", false).findAll();
for(Stuff stuff : stuffs) {
stuff.setSomething(true);
}
The elements in the results still get invalidated though, but the results themselves don't change. (This is the same for snapshot collections as well in Realm 3.0.0+).
0.90.0:
Breaking changes
RealmChangeListener provides the changed object/Realm/collection as well (#1594).
All JSON methods on Realm now only wraps JSONException in RealmException. All other Exceptions are thrown as they are.
Marked all methods on RealmObject and all public classes final (#1594).
Removed BaseRealm from the public API.
Removed HandlerController from the public API.
Removed constructor of RealmAsyncTask from the public API (#1594).
RealmBaseAdapter has been moved to its own GitHub repository: https://github.com/realm/realm-android-adapters
File format of Realm files is changed. Files will be automatically upgraded but opening a Realm file with older versions of Realm is not possible.
So RealmBaseAdapter is now in realm-android-adapters, for 1.1.1 of Realm, use 1.3.0. Also adds RealmRecyclerViewAdapter. For 3.5.0, use 2.0.0 or newer.
RealmChangeListeners got an element parameter. Yay.
Also, Date now has milisecond precision.
0.91.0:
Breaking changes
Removed all #Deprecated methods.
Calling Realm.setAutoRefresh() or DynamicRealm.setAutoRefresh() from non-Looper thread throws IllegalStateException even if the autoRefresh is false (#2820).
Deprecated a lot of methods in 0.90.0, so
Breaking Changes:
Realm.allObjects*(). Use Realm.where(clazz).findAll*() instead.
Realm.distinct*(). Use Realm.where(clazz).distinct*() instead.
DynamicRealm.allObjects*(). Use DynamicRealm.where(className).findAll*() instead.
DynamicRealm.distinct*(). Use DynamicRealm.where(className).distinct*() instead.
Realm.allObjectsSorted(field, sort, field, sort, field, sort). Use RealmQuery.findAllSorted(field[], sort[])` instead.
RealmQuery.findAllSorted(field, sort, field, sort, field, sort). Use RealmQuery.findAllSorted(field[], sort[])` instead.
RealmQuery.findAllSortedAsync(field, sort, field, sort, field, sort). Use RealmQuery.findAllSortedAsync(field[], sort[])` instead.
RealmConfiguration.setModules(). Use RealmConfiguration.modules() instead.
Realm.refresh() and DynamicRealm.refresh(). Use Realm.waitForChange()/stopWaitForChange() or DynamicRealm.waitForChange()/stopWaitForChange() instead.
waitForChange() doesn't really work as people would intend to use it, so here's a workaround for 1.1.1 to 3.1.4 though, should be used only on background threads. refresh() will be re-added in 3.2.0.
Also, at some point Realm.getInstance(Context) was removed, use Realm.getInstance(new RealmConfiguration.Builder(Context).build()) instead.
After that, 1.0.0 came, so it's pretty much just that.
By the way, in 1.1.0, insertOrUpdate() was added which is faster than copyToRealmOrUpdate(), and doesn't return a proxy.
2.0.2:
Primary keys are immutable on managed objects, once it's set, it cannot be changed, and it throws an exception if you try. Also, use realm.createObject(clazz, primaryKeyValue) if you use createObject() to create objects.
You must call Realm.init(Context) at some point.
Configuration Builder no longer receives Context.
armeabi is no longer supported. (only v7a and the others)
No breaking changes until 3.0.0, but a ton of bugfixes.
3.0.0:
RealmResults.distinct() returns a new RealmResults object instead of filtering on the original object (#2947).
RealmResults is auto-updated continuously. Any transaction on the current thread which may have an impact on the order or elements of the RealmResults will change the RealmResults immediately instead of change it in the next event loop. The standard RealmResults.iterator() will continue to work as normal, which means that you can still delete or modify elements without impacting the iterator. The same is not true for simple for-loops. In some cases a simple for-loop will not work (https://realm.io/docs/java/3.0.0/api/io/realm/OrderedRealmCollection.html#loops), and you must use the new createSnapshot() method.
RealmChangeListener on RealmObject will now also be triggered when the object is deleted. Use RealmObject.isValid() to check this state(#3138).
RealmObject.asObservable() will now emit the object when it is deleted. Use RealmObject.isValid() to check this state (#3138).
Removed deprecated classes Logger and AndroidLogger (#4050).
Due to the Realm ObjectStore Results integration, RealmResults is live again in transactions, just like back in 0.88.3 and before.
So simple for loops (indexing with for(int i = 0; ...) is prone to break -
meaning you either need to reverse iterate them, or create a snapshot collection first.
OrderedRealmCollection<Thing> snapshot = results.createSnapshot();
for(int i = 0; i < snapshot.size(); i++) { ...
Also, RealmObject change listener will now also emit on deletion, you need to check for isValid() in the change listener. This is so that you can update the UI if the object has been deleted in the background.
3.1.0:
Updated file format of Realm files. Existing Realm files will automatically be migrated to the new format when they are opened, but older versions of Realm cannot open these files.
Nothing to do here, but it's worth a mention.
3.2.0-3.2.1:
Nothing to do here, except update proguard, because there was a bug introduced here. Added proguard section.
3.3.0: (and 3.3.1)
Nothing to do here, the bug was fixed which caused the Proguard problem in 3.2.0.
3.4.0:
Nothing to do here, although it's worth looking at the new #LinkingObjects API for inverse relationships.
In fact, it is recommended to replace bi-directional links with uni-directional link + inverse relationship.
3.5.0:
Breaking Changes
An IllegalStateException will be thrown if the given RealmModule doesn't include all required model classes (#3398).
If you haven't specified all RealmObjects in the modules() (in case you use multiple modules instead of just the default, for example RealmObjects from a library project), then you need to make sure you actually provide all RealmObjects that are part of the schema in your modules.
Previously it silently added them even if they were not in the modules, now that is not the case.
4.0.0:
Breaking Changes
The internal file format has been upgraded. Opening an older Realm
will upgrade the file automatically, but older versions of Realm will
no longer be able to read the file.
[ObjectServer] Updated protocol version to 22 which is only compatible
with Realm Object Server >= 2.0.0.
[ObjectServer] Removed deprecated
APIs SyncUser.retrieveUser() and SyncUser.retrieveUserAsync(). Use
SyncUser.retrieveInfoForUser() and retrieveInfoForUserAsync() instead.
[ObjectServer] SyncUser.Callback now accepts a generic parameter
indicating type of object returned when onSuccess is called.
[ObjectServer] Renamed SyncUser.getAccessToken to
SyncUser.getRefreshToken.
[ObjectServer] Removed deprecated API
SyncUser.getManagementRealm().
Calling distinct() on a sorted
RealmResults no longer clears any sorting defined (#3503).
Relaxed
upper bound of type parameter of RealmList, RealmQuery, RealmResults,
RealmCollection, OrderedRealmCollection and
OrderedRealmCollectionSnapshot.
Realm has upgraded its RxJava1 support
to RxJava2 (#3497) Realm.asObservable() has been renamed to
Realm.asFlowable(). RealmList.asObservable() has been renamed to
RealmList.asFlowable(). RealmResults.asObservable() has been renamed
to RealmResults.asFlowable(). RealmObject.asObservable() has been
renamed to RealmObject.asFlowable(). RxObservableFactory now return
RxJava2 types instead of RxJava1 types.
Removed deprecated APIs
RealmSchema.close() and RealmObjectSchema.close(). Those don't have to
be called anymore.
Removed deprecated API
RealmResults.removeChangeListeners(). Use
RealmResults.removeAllChangeListeners() instead.
Removed deprecated
API RealmObject.removeChangeListeners(). Use
RealmObject.removeAllChangeListeners() instead.
Removed
UNSUPPORTED_TABLE, UNSUPPORTED_MIXED and UNSUPPORTED_DATE from
RealmFieldType.
Removed deprecated API
RealmResults.distinct()/RealmResults.distinctAsync(). Use
RealmQuery.distinct()/RealmQuery.distinctAsync() instead.
RealmQuery.createQuery(Realm, Class),
RealmQuery.createDynamicQuery(DynamicRealm, String),
RealmQuery.createQueryFromResult(RealmResults) and
RealmQuery.createQueryFromList(RealmList) have been removed. Use
Realm.where(Class), DynamicRealm.where(String), RealmResults.where()
and RealmList.where() instead.
So Rx1 support was replaced with Rx2 support, and removeChangeListeners() was renamed to removeAllChangeListeners().
Most other things only affect sync Realms, and from this point it is possible to use RealmList<String>, RealmList<Date>, and RealmList<Integer> as part of the Realm schema. Querying them is not yet supported, and they are not filled out by create*FromJson methods.
4.3.1:
Deprecated
RealmQuery.findAllSorted() and RealmQuery.findAllSortedAsync()
variants in favor of predicate RealmQuery.sort().findAll().
RealmQuery.distinct() and RealmQuery.distinctAsync() variants in favor
of predicate RealmQuery.distinctValues().findAll()
Instead of using realm.where(Blah.class).distinct("something") or realm.where(Blah.class).findAllSorted("something"), you can now do
realm.where(Blah.class)
.distinctValues("something") // subject to change to `distinct()`
.sort("something") // hopefully will change to `sorted()`? // nope, it's `sort`
.findAll();
5.0.0:
Renamed RealmQuery.distinctValues() to RealmQuery.distinct()
Removed previously deprecated RealmQuery.findAllSorted(), RealmQuery.findAllSortedAsync() RealmQuery.distinct() andRealmQuery.distinctAsync()`.
The OrderedCollectionChangeSet parameter in OrderedRealmCollectionChangeListener.onChange() is no longer nullable. Use changeSet.getState() instead (#5619).
So this means that realm.where(...).findAllSorted("field") should be realm.where(...).sort("field").findAll().
Also comes that OrderedRealmCollectionChangeListener used to send null as the initial change set, now that is no longer the case, and == null should be replaced with .getState() == OrderedCollectionChangeSet.State.INITIAL. This also means that you need to use realm-android-adapters 3.0.0 or newer with Realm 5.0+.
Also, if you relied on the names of __RealmProxy classes: they are named by their fully qualified name, including packages, like my_package_SomeObjectRealmProxy.
PROGUARD RULES
#realm older than 0.84.1
-keepnames public class * extends io.realm.RealmObject
-keep #io.realm.annotations.RealmModule class *
-keep class io.realm.** { *; }
-dontwarn javax.**
-dontwarn io.realm.**
#realm 0.84.1+ and older than 1.0.0
-keep class io.realm.annotations.RealmModule
-keep #io.realm.annotations.RealmModule class *
-keep class io.realm.internal.Keep
-keep #io.realm.internal.Keep class *
-dontwarn javax.**
-dontwarn io.realm.**
#realm 0.89.0+ and older than 1.0.0
-keep class io.realm.RealmCollection
-keep class io.realm.OrderedRealmCollection
#realm 3.2.0 and 3.2.1
-keepnames public class * extends io.realm.RealmObject

The main upgrade to Realm from 4+ to 5+ needs to change from:
realm.where(example.class)
.findAllSorted("field")
To:
realm.where(example.class)
.sort("field")
.findAll();

Related

LifecycleObserver produce exception with methods that use newer APIs

My ViewModel class implements LifecycleObserver.
When I call fragment.lifecycle.addObserver(this) it produces exception.
Caused by: java.lang.IllegalArgumentException: The observer class has some methods that use newer APIs which are not available in the current OS version. Lifecycles cannot access even other methods so you should make sure that your observer classes only access framework classes that are available in your min API level OR use lifecycle:compiler annotation processor.
Strange, that firstly it was working fine, but not long ago this exception has appeared. I've found, that audioFocusRequest is cause of this bug.
private val audioFocusRequest by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setOnAudioFocusChangeListener(this)
.build() else throw RuntimeException("Can't be done for Android API lower than 26")
}
Does anybody know how it can be fixed?
UPD
Tried to use annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version", but got compilation error:
(decided to paste screenshot, because whole logs are quite big)
UPD 2
At the end I've decided to delete audioFocusRequest field and to use old deprecated method - requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) instead of recommended requestAudioFocus(#NonNull AudioFocusRequest focusRequest)
It helped me to make code working again, so it can be solution. But I didn't find answer - why this problem had appeared. It strange because code used to be working before.
So problem has been solved but question still stays unanswered
Try to use kapt "androidx.lifecycle:lifecycle-compiler:2.0.0"
The class which implements LifecycleObserver has some method, which has parameters with type that only exist for higher APIs.
Your variables (i guess) and function parameters must exist on all APIs even function is not called (maybe this is requirement for classes who implement LifecycleObserver).
A possible solution is to change function parameter type to Any (kotlin) or Object (Java) and cast it to appropriate type inside function.
I have to remove this set method on SpinnerView: lifecycleOwner = viewLifecycleOwner
I was able to fix this by moving the offending methods into another class, but still called from my LifecycleObserver. After reading the error message again:
Caused by: java.lang.IllegalArgumentException: The observer class has some methods that use newer APIs which are not available in the current OS version. Lifecycles cannot access even other methods so you should make sure that your observer classes only access framework classes that are available in your min API level OR use lifecycle:compiler annotation processor.
It seems as though no methods or objects are allowed in the class extending LifecycleObserver if they don't exist in the device's OS, even if they are wrapped in an SDK version check and never accessed.

How to format an ObservableField (double) DataBinding?

So, I have the very simple problem, that I cannot seem to format a double coming from an ObservableField in a data binding. I have the following layout:
android:text='#{String.format("%.2f€", transaction.value)}'
and here the definition of transaction.value:
public final ObservableField<Double> value = new ObservableField<>();
I always get this error:
java.util.IllegalFormatConversionException: f != android.databinding.ObservableField
I get the same issue if I use an ObservableDouble. The only way to avoid this seems to be if I call transaction.value.get() in the binding, but I was under the impression that get/set can be omitted here, as I am successfully doing e.g. with an ObservableField<Date>.
I am targeting Sdk 26 with with buildTools 26.0.2.
Update 1
I tried the same thing now with my previous setup, and it worked, just as I remembered. So I pinpointed it to the Gradle version change from 2.3.1 to 3.0.1 (even when I put in target/compile Sdk 26 and buildToolsVersion 26.0.2 with Gradle 2.3.1 it works).
As #dominicoder pointed out to look into the generated data bindings, here is the difference, explaining the problem:
Gradle 2.3.1:
android.databinding.ObservableField<java.lang.Double>transactionValue=null;
java.lang.Double transactionValueGet=null;
....
if(transaction!=null){
// read transaction.value
transactionValue=transaction.value;
}
updateRegistration(3,transactionValue);
if(transactionValue!=null){
// read transaction.value.get()
transactionValueGet=transactionValue.get();
}
// read String.format("%.2f€", transaction.value.get())
stringFormatJavaLangString2fTransactionValue=java.lang.String.format("%.2f€",transactionValueGet);
Gradle 3.0.1:
android.databinding.ObservableField<java.lang.Double> transactionValue = null;
// NO transactionValueGet field !!!
....
if(transaction!=null){
// read transaction.value
transactionValue=transaction.value;
}
updateRegistration(3,transactionValue);
// read String.format("%.2f€", transaction.value)
stringFormatJavaLangString2fTransactionValue=java.lang.String.format("%.2f€",transactionValue);
So - it looks like this is a bug introduced in that version update (to be safe, I created a completely new project with a similar setting, a TextView having its text bound to an ObservableField<Double> using String.format()). Or maybe it is intended behavior, but I really wouldn't understand the purpose of that kind ob breaking change.
Update 2
Currently the problem is filed in the Android issue tracker
The Problem
Check the databinding generated java file for your layout. You will find something like this:
android.databinding.ObservableField<java.lang.Double> transactionValue = null;
// read String.format("%.2f€", accountViewModel.test)
stringFormatJavaLangString2fAccountViewModelTest = java.lang.String.format("%.2f€", transactionValue);
You can see that it's using the observable field literally and I think it's because the signature of String.format is (String string, Object args...). Because the observable field is an Object it can be used as is, so the code generator does not try to "get" the double value instead.
The Date formatting example works because the format method has a signature of (Date date). Thus, the code generator realizes that the signature doesn't match, but the observable type does, so it "get"s it for you and you'll see something like this:
java.util.Date transactionDateGet = null;
// read transaction.date.get()
transactionDateGet = transactionDate.get();
androidTextFormatDateFormatGetDateFormatContextFormatTransactionDate =
android.text.format.DateFormat.getDateFormat(
getRoot().getContext()).format(transactionDateGet);
The Solution
As you already indicated, the easiest thing to do is just manually call .get() in your binding. However, I'd recommend avoiding doing logic in your XML, as #elmorabea indicated. Instead, I'd suggest using a view model that encapsulates the display logic:
public class TransactionViewModel {
private final Transaction mTransaction;
public TransactionViewModel(#NonNull Transaction transaction) {
mTransaction = transaction;
}
#Bindable
public String getTransactionValueText() {
return String.format("%.2f€", transaction.getValue());
}
}
Then you have something like this:
android:text='#{viewModel.transactionValueText}'
This is more work as you have to have an extra class for each layout file; you'd have to move your observable fields from your models to the viewmodels; and you'd have to make your view model obvserve the model to propagate the changes up the to view, but the separation of concerns makes it far easier to update, extend, reuse, and maintain in the long run.
Hope that helps!

How to configure lint to ignore specific cases?

Sometimes lint produces false warnins if field is operated via bunch of annotations. For a common example:
#SerializedName("id") #Expose private Integer id;
field id is assigned only via gson.fromJson(). Such operation is invisible for lint, thus it throws warning variable id is never assigned
So I want to configure lint, in this particular case, to ignore checks if field is ever assigned, if it is annotated with #SearializedName (please dont suggest raw #SuppressWarnings("unused") which has to be set manualy for every field and will block checks, if field is ever used)
Gson serialises fields using reflection, which takes place at runtime. That UnusedAssignment inspection finds variables which meet any of the following criteria:
the variable never gets read after assignment
the value is always overwritten with another assignment before the next variable read
the variable initializer is redundant (for one of the above two reasons) - the variable is never used.
Your only option to get rid of warnings for this specific inspection is to suppress them, either by updating your lint.xml, or via #SuppressWarnings("unused"). If you really don't want to do either of those things, then it may be possible to write a custom Lint inspection that ignores fields with a #SerializedName annotation.
The disadvantage of this approach is if you want to use #SerializedName in a class that isn't serialised by Gson, you will no longer get a warning if a variable is unused. It is also much more complicated than adding #SuppressWarnings("unused") to the top of your GSON model classes.

Realm or Paper for JPA in Android?

I'm developing an Android app with Android Annotations. For persistence, I firstly used a Content Provider (very complex) on top of SQLite. Then, I discovered Realm. It seemed very cool until I had to be notified for insertions to make my RecyclerView dynamic. To be notified of insertions, I made a singleton class I called RealmProxy with a proxy method for copyToRealm(), and an interface to implement to be a RealmListener. I called registered listeners in my copyToRealm() method passing them the added RealmObject, so I could populate my SortedList (support library list designed for RecyclerView) RecyclerView Adapter. I also used my RealmListener interface to send new Objects over network as soon as they are saved.
After compiling and running, I got and IllegalStateException (Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.) because I get the Realm instance from UI thread but I send them over network in a background thread obviously. Why do I get this error ? Whenever my JSON serialization library LoganSquare, based on Jackson calls a getter on my RealmObject in the background to send over network, this exception is thrown. This made me hate Realm thread policy and the fact that fine grained notifications aren't built-in. Also, Realm doesn't allow me to define any custom method. I can't even implement Comparable in a Realm Object.
When I saw Paper (thanks to Android Arsenal and Pushbullet) today, I was very interested in a no headaches JPA solution. It seems very simple, without restriction for Lists, Maps, and any class not extending a special class (Realm requires extending RealmObject and using RealmList instead of generic List which my json<>java didn't liked, forcing me to copy lists).
EDIT:
I discovered SnappyDB today. It uses the same serialization library (Kryo) as Paper, it seems to be very similar to Paper, with more features for keys management.
So my question is the following:
Should I search for workarounds and continue to use Realm, if yes, which workarounds, or should I use Paper, or SnappyDB instead ? Did anyone used Paper or SnappyDB for android?
All the best
If your question is about how to update your Object in UI thread when it gets changed in background, it is actually quite simple.
For example in your UI thread you can do this:
private Dog dog;
private RealmChangeListener listener = new RealmChangeListener() {
#Override
// This will be called when the commitTransaction gets called
// in the background thread
public void onChange() {
// It would changed to "EFG" automatically in next UI loop after
// you updated it in the background thread.
String name = dog.getName();
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
dog = realm.where(Dog.class).equalTo("id", 42).findFirst();
// Assume it is "ABC" now
String name = dog.getName();
// Register the listener
realm.addChangeListener(listener);
}
And update the dog in th background service like:
// This realm will be a different instance created in this thread.
dog = realm.where(Dog.class).equalTo("id", 42).findFirst();
realm.beginTransaction();
dog.setName("EFG");
realm.commitTransaction();
The IllegalStateException is because of:
The only rule to using Realm across threads is to remember that Realm, RealmObject or RealmResults instances cannot be passed across threads. When you want to access the same data from a different thread, you should simply obtain a new Realm instance (i.e. Realm.getInstance(Context context) or its cousins) and get your objects through a query. The objects will map to the same data on disk, and will be readable & writeable from any thread!
See see doc here
And you probably need RealmBaseAdapter which can make building a ListView with Realm pretty easy. You can find example here.
JPA is not a solution, it's a definition for Java Persistence. Once you choose JPA, you need to find an implementation. In the Java world, the most widely used implementation is Hibernate. Also, you can use Hibernate ORM without using JPA.
On Android, OrmLite provides an implementation for a subset of JPA. But, as it's only a subset, you may as well skip JPA and use the equivalent Ormlite annotations. I use JPA implemented by Hibernate on my server apps, and Ormlite with no JPA on Android. I definitely recommend Ormlite.

Android ORMLite 4.38 allowGeneratedIdInsert causing IllegalStateException

I'm targeting Android 2.2 and newer. This error was generated on a device running 4.x. I am using ORMLite 4.38 libraries.
I need to guarantee every record instance is unique for any number of devices. I was happy to see that ORMLite supports UUIDs as IDs. I've created a UUID - id abstract base class for my database record definitions. allowGeneratedIdInsert is the perfect solution. But this feature seems to cause an 'IllegalStateException: could not create data element in dao'. I tested by removing this annotation, and no issue. Put it back in...same issue. Put the base class stuff in one record definition...same issue.
LogCat also reports:
Caused by: java.sql.SQLException: Unable to run insert stmt on object - objectid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx
public abstract class UUIDDaoEnabled<T> extends BaseDaoEnabled<T, UUID> {
//allowGeneratedIdInsert allows us to set UUID when this device db didn't create it
#DatabaseField(generatedId = true, allowGeneratedIdInsert=true)
private UUID id;
...
public void setUUIDFromSerializedSource(SerializedModelBinaryInputStream stream, Dao<T, UUID> dao) throws SQLException { //only place we can set UUIDs
if(id == null)
dao.refresh((T)this);
if(id != null)
throw new SQLException("Trying to set UUID on existing object");
id = stream.getCurrentUUID();
}
}
I'll specialize like so:
#DatabaseTable()
public class Type extends UUIDDaoEnabled<Type> { ... }
I can't explain this from the documentation for allowGeneratedIdInsert and generatedId. In fact the documentation for alloeGeneratedIdInsert says it overrides the default behavior of generatedId. It also says
This only works if the database supports this behavior
Yet, I have read in other posts that ORMLite 4.25 (?) and newer supports this behavior on Android devices. So, either that's not entirely true. Or I'm doing something stupid...anyone???
UPDATE: after thinking about it for a minute, I realized that neither allowGeneratedIdInsert support, nor inheritance can be the root cause, because I instantiate other objects based on the same abstract class. What I can't figure out is why one particular class is causing the issue. The only unique thing about the offending record type (compared to other types that create) is it is a many in a one to many, and it contains several to manies. Could these properties, combined with allowGenereatedIdInsert, be the root issue? Rather, I should ask, has anyone seen this issue in this circumstance?
UPDATE: nevermind the question. I can use updateId(...) instead of allowGeneratedIdInsert.
So I'm not sure about this but it looks to me that you are trying to insert an element twice into a table with the same UUID id. The exception is saying there is a constraints failure:
IllegalStateException: Could not create data element in dao
at BaseForeignCollection.add(BaseForeignCollection.java:57)
...
Caused by: SQLiteConstraintException: error code 19: constraint failed
If you call foreignCollection.add(...); it does the same thing as dao.create(...); -- and you can't do both of these with the same object. If you have an existing object that has already been created by the DAO and you want to associate it with another object, you should do something like:
// associate this object with another
existingObject.setForeignField(...);
// now update it in the db
existingObjectDao.update(existingObject);
You can't add it to the foreignField's foreign collection.
I had a similar problem. But it was caused by using create instead createOrUpdate to save the object.
It is also important to uninstall the application before changing this to ensure that the database has been removed and will not keep the old behavior.
Edit: createOrUpdate is very time expensive. It's better use just create with great amounts of data.
Edit 2:It is also bether to use a TransactionManager.callInTransaction.

Categories

Resources