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;
});
Related
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.
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).
This question is a follow-up question from: Organize Android Realm data in lists
Due to the data returned by the API we use, it's slightly impossible to do an actual query on the realm database. Instead I'm wrapping my ordered data in a RealmList and adding a #PrimaryKey public String id; to it.
So our realm data looks like:
public class ListPhoto extends RealmObject {
#PrimaryKey public String id;
public RealmList<Photo> list; // Photo contains String/int/boolean
}
which makes easy to write to and read from the Realm DB by simply using the API endpoint as the id.
So a typical query on it looks like:
realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
This creates a slightly overhead of listening/subscribing to data because now I need to check listUser.isLoaded() use ListUser to addChangeListener/removeChangeListener and ListUser.list as an actual data on my adapter.
So my question is:
Is there a way I can query this realm to receive a RealmResults<Photo>. That way I could easily use this data in RealmRecyclerViewAdapter and use listeners directly on it.
Edit: to further clarify, I would like something like the following (I know this doesn't compile, it's just a pseudo-code on what I would like to achieve).
realm
.where(ListPhoto.class)
.equalTo("id", id)
.findFirstAsync() // get a results of that photo list
.where(Photo.class)
.getField("list")
.findAllAsync(); // get the field "list" into a `RealmResults<Photo>`
edit final code: considering it's not possible ATM to do it directly on queries, my final solution was to simply have an adapter that checks data and subscribe if needed. Code below:
public abstract class RealmAdapter
<T extends RealmModel,
VH extends RecyclerView.ViewHolder>
extends RealmRecyclerViewAdapter<T, VH>
implements RealmChangeListener<RealmModel> {
public RealmAdapter(Context context, OrderedRealmCollection data, RealmObject realmObject) {
super(context, data, true);
if (data == null) {
realmObject.addChangeListener(this);
}
}
#Override public void onChange(RealmModel element) {
RealmList list = null;
try {
// accessing the `getter` from the generated class
// because it can be list of Photo, User, Album, Comment, etc
// but the field name will always be `list` so the generated will always be realmGet$list
list = (RealmList) element.getClass().getMethod("realmGet$list").invoke(element);
} catch (Exception e) {
e.printStackTrace();
}
if (list != null) {
((RealmObject) element).removeChangeListener(this);
updateData(list);
}
}
}
First you query the ListPhoto, because it's async you have to register a listener for the results. Then in that listener you can query the result to get a RealmResult.
Something like this
final ListPhoto listPhoto = realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
listPhoto.addChangeListener(new RealmChangeListener<RealmModel>() {
#Override
public void onChange(RealmModel element) {
RealmResults<Photo> photos = listPhoto.getList().where().findAll();
// do stuff with your photo results here.
// unregister the listener.
listPhoto.removeChangeListeners();
}
});
Note that you can actually query a RealmList. That's why we can call listPhoto.getList().where(). The where() just means "return all".
I cannot test it because I don't have your code. You may need to cast the element with ((ListPhoto) element).
I know you said you're not considering the option of using the synchronous API, but I still think it's worth noting that your problem would be solved like so:
RealmResults<Photo> results = realm.where(ListPhoto.class).equalTo("id", id).findFirst()
.getList().where().findAll();
EDIT: To be completely informative though, I cite the docs:
findFirstAsync
public E findFirstAsync()
Similar to findFirst() but runs asynchronously on a worker thread This method is only available from a Looper thread.
Returns: immediately an empty RealmObject.
Trying to access any field on the returned object before it is loaded
will throw an IllegalStateException.
Use RealmObject.isLoaded() to check if the object is fully loaded
or register a listener RealmObject.addChangeListener(io.realm.RealmChangeListener<E>) to be
notified when the query completes.
If no RealmObject was found after
the query completed, the returned RealmObject will have
RealmObject.isLoaded() set to true and RealmObject.isValid() set to
false.
So technically yes, you need to do the following:
private OrderedRealmCollection<Photo> photos = null;
//...
final ListPhoto listPhoto = realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
listPhoto.addChangeListener(new RealmChangeListener<ListPhoto>() {
#Override
public void onChange(ListPhoto element) {
if(element.isValid()) {
realmRecyclerViewAdapter.updateData(element.list);
}
listPhoto.removeChangeListeners();
}
}
I have following class
public class Student extends RealmObject{
private int studentID;
private String studentName;
// getters and setters here
}
Then I try to set a value to a already created student object
student.setStudentName("Peter");
Then I get following error
java.lang.IllegalStateException: Mutable method call during read
transaction.
In order to overcome this I have to do it as follows
Realm realm = Realm.getInstance(this);
realm.beginTransaction();
student.setStudentName("Peter");
realm.commitTransaction();
I don't want to persist this change in the database. How can I just set/change a value to an realm object variable without always persisting it to the database?
If you want to modify the object in a non-persisted manner, you need an unmanaged copy of it.
You can create a copy using realm.copyFromRealm(RealmObject realmObject); method.
When you are using Realm.createObject(), the object is added to the Realm and it only works within a write transaction. You can cancel a transaction and thereby discard the object.
Moreover, you can use your model class as a standalone class and create objects in memory (see http://realm.io/docs/java/0.80.0/#creating-objects for details). If you need to persist the objects, you can use the Realm.copyToRealm() method.
You might want to create a new Model. And your new model should implement RealmModel.
public class StudentRM extends RealmModel{
private int studentID;
private String studentName;
// Constructors here
// getters and setters here
}
Now you can do this.
studentRm.setStudentName("Peter"); //Setting Vale Or
studentRm.addAll(student); //Add all value from DB
studentRm.setStudentName("Jhon"); //It won't change DB anymore
studentRm.getStudentName(); // "Jhon"
You can use realm.cancelTransaction();, instead of realm.commitTransaction();
I want to persist an object with two foreignCollections.
But when I try to query the object, my foreignId is always null.
I already read this answers but it doesn't really help me: Collections in ORMLite
VOPerception perception = new VOPerception();
perception.setOrientation(daoOrientation.createIfNotExists(
orientationLocalizer.getCurrentOrientation()));
ForeignCollection<VOAccessPoint> fAp =
daoPerception.getEmptyForeignCollection("accessPoints");
fAp.addAll(wifiLocalizer.getCurrentScanResultMap());
perception.setAccessPoints(fAp);
daoPerception.create(perception);
List<VOPerception> list = daoPerception.queryForAll();
here data are correctly stored but VOAccessPoint objects have no link with the parent VOPerception object.
Here are my two classes:
public class VOPerception {
#DatabaseField(generatedId=true)
private int per_id;
#ForeignCollectionField(eager=true)
ForeignCollection<VOAccessPoint> accessPoints;
...
}
public class VOAccessPoint{
#DatabaseField(generatedId=true)
private int ap_id;
#DatabaseField(foreign=true,columnName="apForeignPerception_id")
private VOPerception apForeignPerception;
...
}
Your queryForAll() is returning no objects because none of your VOAccessPoint instances ever set their apForeignPerception field to be perception. Adding the VOAccessPoint objects using the ForeignCollection added them to the DAO but did not automagically assign their apForeignPerception field.
You should do something like:
...
Collection<VOAccessPoint> points = wifiLocalizer.getCurrentScanResultMap();
for (VOAccessPoint point : points) {
point.setApForeignPerception(perception);
}
fAp.addAll(points);
...
I can see how you might think that this would be handled automagically but at the time they are added to the ForeignCollection, the perception is not even assigned. I suspect that there is a missing feature for ORMLite here or at least a better exception.
I would recommend to use assignEmptyForeignCollection(Obj parent, fieldName). This will create a new foreign collection and all objects you will add via add(Obj element) will have the parent value set automatically.