Realm instance has already been closed - with RxJava2 - android

I used Realm in conjunction with RxJava it this way:
public Flowable<List<EventEntity>> getAll() {
try (final Realm realm = Realm.getInstance(mRealmConfiguration)) {
RealmQuery<RealmEvent> query = realm.where(RealmEvent.class);
Flowable<RealmResults<RealmEvent>> result;
if (realm.isAutoRefresh()) {
result = query
.findAllAsync()
.asFlowable()
.filter(RealmResults::isLoaded);
} else {
result = Flowable.just(query.findAll());
}
return result
.unsubscribeOn(AndroidSchedulers.mainThread());
}
}
I use this chain on multiple places in app. For example:
return Observable.merge(
mEventRepository.getAll()
.toObservable(),
subjectNotificationChange
.flatMapMaybe(notification ->
mEventRepository.getAll()
.firstElement()
)
)
Problem is that I obtain exception: java.lang.IllegalStateException: This Realm instance has already been closed, making it unusable.
I looked at implementation method from of RealmObservableFactory and each call of subscribe method should create new instance of Realm. Entire situation looks as problem with references counting.
Do you know where is problem?

Java's try-with-resource closes the resource as soon as you leave the code block, but RxJava being lazy and all, only begins working when you actually subscribe, which happens after your code exits the getAll() function.
Edit: since you build a special Realm instance each time, passing configuration to it, the instance is not shared and therefore definitively closed each time.
Instead, initialize your Realm earlier using Realm.setDefaultConfiguration(config). Then, use Realm.getDefaultInstance() in your function so you access the default shared instance instead of creating a new one each time.
Edit2: the easiest solution is to keep a reference to the Realm instance:
class MyRepository {
private final Realm realm;
public MyRepository(Realm realm) {
this.realm = realm;
}
public Flowable<List<EventEntity>> getAll() {
RealmQuery<RealmEvent> query = realm.where(RealmEvent.class);
// ...
}
}
Realm realm = Realm.getDefaultInstance();
MyRepository repository = MyRepository(realm);
repository.getAll()
// ...

I find solution. It is bug in official example. When you call mentioned chain than must exist other open Realm instance for same thread. In other cases RealmResult is invalidated. Can be used solution mentioned by ESala.

Related

How to delete element from database realm Kotlin

I need to delete elements from the database.
That's my code and I don't know if I am right
realm.executeTransaction(
realm1 -> {
RealmResults<UserWordRealm> result = realm1.where(UserWordRealm.class).equalTo("id",id).findAll();
result.deleteAllFromRealm();
}
);
}
In the RealmObject class, the id(PrimaryKey) field must uniquely identify the object. Therefore, there cannot be more than one element with the same id. Using findFirst() instead of findAll() may solve your problem.
To delete an object from a realm, use either the dynamic or static versions of the deleteFromRealm() method of a RealmObject subclass.
realm.executeTransaction(r -> {
UserWordRealm userWordObject = r.where(UserWordRealm.class).equalTo("id", id).findFirst();
userWordObject.deleteFromRealm();
// discard the reference
userWordObject = null;
});

Realm doesn't find existing items

I'm using Realm 3.4.0 and having one object that should be a singleton. The database is synced.
Here is a simplified version of the code: test if that object exist, add it if it doesn't exist. What is the correct way to do that? (copyToRealmOrUpdate shouldn't be needed or is there any other reason why the instance becomes null?)
#PrimaryKey
public long id = 1;
public static PlannerManager getInstance(Realm realm) {
PlannerManager ourInstance = null;
if (instanceLock == null)
instanceLock = new ReentrantLock();
try {
instanceLock.lock();
realm.refresh(); // Force getting all data from online database
ourInstance = realm.where(PlannerManager.class).findFirst();
if (ourInstance == null) { // The item doesn't exist
realm.beginTransaction();
ourInstance = realm.copyToRealm(new PlannerManager()); // Crashes sometimes with the error that an object with primary ID already exists
realm.commitTransaction();
}
} finally {
instanceLock.unlock();
}
return ourInstance;
}
Relevant part of the stacktrace
2:9.446 Primary key value already exists: 1 .
(/Users/cm/Realm/realm-java/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp:189) io.realm.exceptions.RealmPrimaryKeyConstraintException: Primary key value already exists: 1 .
(/Users/cm/Realm/realm-java/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp:189)
at io.realm.internal.OsObject.nativeCreateNewObjectWithLongPrimaryKey(Native Method)
at io.realm.internal.OsObject.createWithPrimaryKey(OsObject.java:198)
at io.realm.Realm.createObjectInternal(Realm.java:1052)
at io.realm.PlannerManagerRealmProxy.copy(PlannerManagerRealmProxy.java:1279)
at io.realm.PlannerManagerRealmProxy.copyOrUpdate(PlannerManagerRealmProxy.java:1268)
at io.realm.DefaultRealmModuleMediator.copyOrUpdate(DefaultRealmModuleMediator.java:438)
at io.realm.Realm.copyOrUpdate(Realm.java:1660)
at io.realm.Realm.copyToRealm(Realm.java:1072)
at com.myapp.internal.PlannerManager.getInstance(PlannerManager.java:857)
Thanks!
Your logic is actually slightly wrong. By doing the query outside the transaction, the background sync thread might put data into Realm between you do the query and begin the transaction. Transactions will always move the Realm to the latest version, so calling refresh() is also not needed. Your logic should be something like:
realm.beginTransaction();
ourInstance = realm.where(PlannerManager.class).findFirst();
if (ourInstance == null) {
ourInstance = realm.createObject(PlannerManager.class, 1);
realm.commitTransaction();
} else {
realm.cancelTransaction();
}
Note, that using realm.copyToRealm() will cause changes from other devices to be overridden, so for initial data like this, it is safer to use createObject as changes to individual fields will then merge correctly. Using copyToRealm() is the same as actually setting all fields to the initial value.
E.g if you have two devices A and B that are both offline:
A starts app and creates the default PlannerManager.
A modifies a field in PlannerManager.
B starts the app, but since A is offline, it doesn't know that PlannerManager is already created, so it also creates the default PlannerManager.
A and B both go online.
Due to how Realm uses "last-write-wins", B will now override all changes done by A, since using copyToRealm is the equivalent of setting all fields manually.
Using Realm.createObject() uses a special "default" instruction for all fields, that automatically loses to any explicit set like the one used when using normal Java setters (and which copyToRealm uses).

Realm with RxAndroid not picking up latest data changes

I am using Realm with RxAndroid. i am having this strange issue where realm is not picking up the latest modification done on DB.
There are 2 methods that i am using.
Observable<Integer> save(Bitmap bitmap).
Observable<Integer> getImageList(Context applicationContext).
Like this
Activity 1
getImageList(applicationContext)
button click -> Activity 2
save(bitmap)
finish()
getImageList(applicationContext)
This method "save" basically adds a newly created model into RealmList.
private Observable<Integer> save(Bitmap bitmap) {
return Observable.create((Observable.OnSubscribe<Integer>) subscriber -> {
--------------------------------------
-----Various file creation stuff------
--------------------------------------
UserImagesModel model = realm
.where(UserImagesModel.class)
.findFirst();
//ImageModel class extends RealmObject
ImageModel imageModel = new ImageModel();
realm.beginTransaction();
//realm object must be Edited inside transaction
model.getResponse().add(0, imageModel);
realm.commitTransaction();
realm.close();
subscriber.onNext(1);
subscriber.onCompleted();
}
}
Ans this method fetches saved list.
public Observable<Integer> getImageList(Context applicationContext) {
return Observable.create((Observable.OnSubscribe<Integer>) subscriber -> {
AppUtils.logD("User image observable instance " + this);
UserImagesModel model;
Realm realm = Realm.getInstance(applicationContext);
model = realm.where(UserImagesModel.class).findFirst();
^
This model doesn't replicate data added in save call
------------------------------------------------
----Various validation and service calls.-------
------------------------------------------------
subscriber.onCompleted();
realm.close();
});
}
}
As i mentioned in code, UserImageModel that i get from Realm doesn't replicate changes i made in save method.
the problem occurs when i call getImageList method second time. also when i print this.toString inside Observable.create it prints same object that was returned first time.
So i believe this issue seems to be with the way i am using RxAndroid. can anyone tell me what i am missing? and how can i resolve it?
UPDATE :
After few tests i realized that this.toString inside Observable.create is actually points to parent object as i have used lamda expression so that is not seems to be the issue and now i am back to square one ;(
Turns out, this is expected behavior of Realm. as i was subscribing those observables on IO threads which doesn't have Looper.
Op here has similar issue. answer explains the case.

Android realm.io variables not set?

I am having trouble using a query in realm.io. My code:
public static void delete(Context context, Workday workday) {
Realm realm = getRealm(context);
realm.beginTransaction();
RealmResults<Workday> workdays = realm.where(Workday.class)
.equalTo("date", workday.getDate())
.equalTo("hours", workday.getHours())
.equalTo("minutes", workday.getMinutes())
.findAll();
workdays.remove(0);
realm.commitTransaction();
}
Debug:
Why is the data visible in the JSON line but not in the fields itself? What am I doing wrong?
Thanks in advance!
UPDATE:
This is my delete function and it does find the workday1 object:
public static void delete(Context context, Workday workday) {
Realm realm = getRealm(context);
realm.beginTransaction();
Workday workday1 = realm.where(Workday.class)
.equalTo("date", workday.getDate())
.equalTo("hours", workday.getHours())
.equalTo("minutes", workday.getMinutes())
.findFirst();
workday1.removeFromRealm();
realm.commitTransaction();
}
When it executes the removeFromRealm method it crashes:
java.lang.IllegalStateException: Illegal State: Row/Object is no longer valid to operate on. Was it deleted?
How can I fix this?
You're doing nothing wrong! :)
Realm proxies your objects so that there won't be copies of your data all over the place. The getters and setters are overridden by the proxy classes and access your data directly in Realm. Of course this makes it harder to inspect the objects during debug, as you could notice, but that's why we've also overridden toString() to show something meaningful.

Android realm.io: Row/Object is no longer valid

This is my delete function and it does find the workday1 object:
public static void delete(Context context, Workday workday) {
Realm realm = getRealm(context);
realm.beginTransaction();
Workday workday1 = realm.where(Workday.class)
.equalTo("date", workday.getDate())
.equalTo("hours", workday.getHours())
.equalTo("minutes", workday.getMinutes())
.findFirst();
workday1.removeFromRealm();
realm.commitTransaction();
}
When it executes the removeFromRealm method it crashes:
java.lang.IllegalStateException: Illegal State: Row/Object is no longer valid to operate on. Was it deleted?
How can I fix this? Any help would be greatly appreciated.
UPDATE (I can print the content returned by the following method):
Workday workday1 = realm.where(Workday.class)
.equalTo("date", workday.getDate())
.equalTo("hours", workday.getHours())
.equalTo("minutes", workday.getMinutes())
.findFirst();
System.out.println("--------------------------------");
System.out.println(workday1.getHours());
You are trying to remove an object you have not committed to the Realm yet.
In this particular case, if for some reason you don't want to commit the object anymore, you can simply cancel the transaction.
In my case, the problem was that the same Object I was deleting was in an Adapter. After I made the Adapter extend RealmBaseAdapter the problem stopped.

Categories

Resources