How works delete in realm with relationship? - android

I have this classes
class Student extends RealmObject {
public String code;
public String name;
public String email;
public Course course;
}
class Course extends RealmObject {
public String code;
public String name;
}
class Sync {
// ...
// To sync data I am using retrofit, look the method to update course
public void onResponse(Call<...> call, Response<...> response) {
if (response.isSuccessful()) {
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.delete(Course.class);
realm.copyToRealm(response.body());
}
});
}
}
}
After call Sync to update Courses, all Student object has its course setting to null, this is expected behavior after called realm delete?
Even after table is populated again, the course on Student is still null.
Today I made this change on the code:
class Course extends RealmObject {
#PrimaryKey
public String code;
public String name;
}
class Sync {
// ...
// To sync data I am using retrofit, look the method to update course
public void onResponse(Call<...> call, Response<...> response) {
if (response.isSuccessful()) {
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(response.body());
}
});
}
}
}
I made this too late to avoid delete the courses.
There is something that can I do to recovery the references courses and set it again to student?
Thank you.

This is expected behavior, because you invalidate the object links by deleting the objects you are pointing to.
To restore them, you would have to set the links again.
Another solution would be to not delete courses that you still need. This would be done if you annotate code with #PrimaryKey, that way you would "update" courses that are already in. Then the problem would be removing courses/students no longer in the response, but there are solutions ready-made for that.
public class Robject extends RealmObject {
#PrimaryKey
private String code;
#Index
private String name;
//...
#Index
private boolean isBeingSaved;
//getters, setters
}
And
// background thread
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Robject robject = new Robject();
for(Some some : somethings) {
robject.set(some....);
realm.insertOrUpdate(robject);
}
realm.where(Robject.class)
.equalTo(Robject.IS_BEING_SAVED, false) // compile 'dk.ilios:realmfieldnameshelper:1.1.0'
.findAll()
.deleteAllFromRealm(); // delete all non-saved data
for(Robject robject : realm.where(Robject.class).findAll()) { // realm 0.89.0+
robject.setIsBeingSaved(false); // reset all save state
}
}
});
} finally {
if(realm != null) {
realm.close();
}
}

Related

Android Realm - RealmObject returning after deletion

I'm running in to an issue where I delete a RealmObject (and confirm its gone using StethoRealm), but when I create and save a new RealmObject with the same variable value, the deleted RealmObject reappears along with my new RealmObject.
With the below object - I delete the myObject1 by matching the 'id', but when I create a new MyObject myObject2 with the same 'name' both objects appear in my realm
Any tips on why this might be happening?
Example:
public class MyObject extends RealmObject {
#PrimaryKey
private String id;
private String name;
// getters and setters
}
Edit
Delete call
#Override
public void deleteMyObjectById(final String id, final OnDeleteMyObjectById callback) {
Realm realm = new MyRealmConfiguration().getDefaultRealm();
final RealmResults<MyObject> myObjects = realm.where(MyObject.class).equalTo(RealmTable.MyObject.ID, id).findAll();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
myObjects.deleteAllFromRealm();
if (callback != null) {
callback.onSuccess();
}
}
});
realm.close();
}
Add call
#Override
public void addMyObject(MyObject myObject, OnSaveMyObjectCallback callback) {
Realm realm = new MyRealmConfiguration().getDefaultRealm();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(myObject);
if (callback != null) {
callback.onSuccess();
}
}
});
realm.close();
}
I'm not sure as to how you are deleting your objects. The way I'm deleting them (and it works) is...
final RealmResults<UserCredentials> credentialResults = realm.where(UserCredentials.class).findAll();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
credentialResults.deleteAllFromRealm();
}
});
realm.close();
Of course, you are using MyObject instead of UserCredentials, and you aren't deleting everything of that type, but it should be similar enough. Try using executeTransaction and realm.close() if you aren't already.
Try something like this. get the instance inside transcation method and delete. i have been using this for one of my app. and it works fine with deletion process
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmResults<MyObject> myObjects = realm.where(MyObject.class).equalTo(RealmTable.MyObject.ID, id).findAll();
myObjects.deleteAllFromRealm();
}
});

Counters custom conflict resolution in realm mobile platform for android

I want to set a custom resolution for this scenario:
1- increment an integer field in realmobject in one device in offline mode
2- increment the same integer field in same realmobject in another device in offline mode
The default custom resolution is last update wins but in my case I want
the increment in both devices take effect on result after going live not last update.
I tried this code for test:
Realm realm = Realm.getDefaultInstance();
final RealmResults<Number> results= realm.where(Number.class).findAll();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
int num = results.get(0).getNumber()+1;
results.get(0).setNumber(num);
}
});
the Number class is like this:
public class Number extends RealmObject {
#PrimaryKey
private String id;
private int number;
public String getId() {
return id;
}
public void increment(){
this.number++;
}
public void setId(String id) {
this.id = id;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
This problem is very crucial to my app. If I can't do this in client side
I will not be able to use realm mobile platform which I was get so interested in.
Maybe you can use list of commands for such objects, persist them in offline and sync/merge on going online. Commands can be something like increment, decrement, multiplyBy2 and so on.
Documentation says:
Inserts in lists are ordered by time.
If two items are inserted at the same position, the item that was
inserted first will end up before the other item. This means that
if both sides append items to the end of a list they will end up in
order of insertion time.
So you will always have list of applied commands sorted by date.
The documentation currently says that counters are supported by the protocol but not exposed at the language level yet, so I guess you will have to implement it yourself.
The easiest way will be to just store it as a List of integers (1 for increment, -1 for decrement), and then use List.sum() (https://realm.io/docs/java/2.2.1/api/io/realm/RealmList.html#sum-java.lang.String-) to quickly get the aggregate result.
public class Counter extends RealmObject {
private int count;
public int getCount() { return count; }
public void setCount(int count) { this.count = count; }
}
public class Number extends RealmObject {
#PrimaryKey
private String id;
private RealmList<Counter> counters;
public void incrementNumber(){
Counter c = realm.createObject(Counter.class);
c.setCount(1);
this.getCounters().add(c);
}
public int getNumber() {
// Get the aggregate result of all inc/decr
return this.getCounters().sum("count");
}
public void setNumber(int number) {
this.getCounters().deleteAllFromRealm();
Counter c = realm.createObject(Counter.class);
c.setCount(number);
this.getCounters().add(c);
}
public String getId() { return id; }
public void setId(String id) { this.id = id; }
private RealmList<Counter> getCounters() { return counters; }
private void setCounters(RealmList<Counter> counters) { this.counters = counters; }
}
```
Thanks to #ast code example. I also solved the problem by caching command pattern here is my code:
public class CommandPattern extends RealmObject {
#PrimaryKey
private String id;
private String commandName;
public String getCommandName() {
return commandName;
}
public void setCommandName(String commandName) {
this.commandName = commandName;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_increment:
if (isOnline()) {
realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
updateNumberOnRealm();
}
});
realm.close();
} else {
addMethodToCache("increment");
}
public void addMethodToCache(final String methodName) {
realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
commandPattern = new CommandPattern();
commandPattern.setId(UUID.randomUUID().toString());
commandPattern.setCommandName(methodName);
realm.copyToRealmOrUpdate(commandPattern);
}
});
realm.close();
}
public void invokeCachedCommands() {
realm = Realm.getDefaultInstance();
commandsCached = realm.where(CommandPattern.class).findAll();
commandsCached.addChangeListener(new RealmChangeListener<RealmResults<CommandPattern>>() {
#Override
public void onChange(final RealmResults<CommandPattern> element) {
if(!element.isEmpty()) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
for (CommandPattern command : element) {
if(command != null) {
if (command.getCommandName().equals("increment")) {
//updateNumberOnRealm();
RealmResults<Number> results = realm.where(Number.class).findAll();
results.get(0).increment();
command.deleteFromRealm();
}
}
}
}
});
}
}
});
realm.close();
}
before getting increment action done I check online state and if it is offline the increment string cached in Command Pattern object
after going online again those cached commands get invoked by following code:
IntentFilter intentFilter = new IntentFilter(NetworkStateChangeReceiver.NETWORK_AVAILABLE_ACTION);
LocalBroadcastManager.getInstance(this).registerReceiver(new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
boolean isNetworkAvailable = intent.getBooleanExtra(IS_NETWORK_AVAILABLE, false);
if (isNetworkAvailable) {
invokeCachedCommands();
}else{
if(commandsCached != null) {
commandsCached.removeChangeListeners();
}
}
}
}, intentFilter);
this is general custom conflict resolution and can be used for any type of command

Items not getting deleted from realm

io.realm:realm-gradle-plugin:2.0.0'
Android Studio 2.2.2
I am trying to delete objects from the realm database. The items seems to get deleted. But when I close the app and load items from the database the deleted ones still seem to have a reference to them. This is my code below for deleting.
If the delete onSuccess is called I send back the item to be removed from the recyclerview's adapter. Is this the correct way to do this?
#Override
public void deletePerson(final Person person, final DeleteListener deleteListener) {
mRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmResults<Person> results = realm.where(Person.class).equalTo("mId", person.getId()).findAll();
results.deleteAllFromRealm();
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
/* send the person object back to be removed from the recyclerview after success*/
deleteListener.onDeleteSuccess(person);
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
deleteListener.onDeleteFailure(error.getMessage());
}
});
}
And when I load the persons the ones that are deleted seem to have a reference in realm and doesn't seem to be completely removed.
#Override
public void loadPersons(final LoadPersonListener loadPersonListener) {
if(mRealm.isClosed()) {
mRealm = Realm.getDefaultInstance();
}
RealmResults<Person> personsList = mRealm.where(Person.class).findAll();
if(personsList.size() > 0) {
loadPersonListener.onLoadPersonSuccess(personsList);
}
else {
loadPersonListener.onLoadPersonFailure("No items in the database");
}
}
You aren't removing anything from the Realm at the moment, you're just querying. Also, you're accessing the Person you sent in on a background thread, which ought to throw IllegalStateException.
So instead of
#Override
public void deletePerson(final Person person, final DeleteListener deleteListener) {
mRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmResults<Person> results = realm.where(Person.class).equalTo("mId", person.getId()).findAll();
}
You should have
#Override
public void deletePerson(final Person person, final DeleteListener deleteListener) {
final String id = person.getId();
mRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.where(Person.class).equalTo("mId", id).findAll().deleteAllFromRealm();
}
Here in you code you only query data asynchronously. For deletion use .remove() method while looping on result of query.
Suppose, that mId is #Primary key, removal will look like:
RealmResults<Person> results = realm.where(Person.class).equalTo("mId", person.getId()).findFirst().removeFromRealm();

Realm, Passing RealmObjects Models dynamicaly possible?

I have thousands of Strings and depending on the contents I want to add them to different RealmObjects Models, and I have about 10 Realm
Models,
right now what I doing something like this:
if (string.contains("abc")) {
mRealm.beginTransaction();
mRealm.copyToRealmOrUpdate(new ABCModel(string));
mRealm.commitTransaction();
}
if (string.contains("xyz")) {
mRealm.beginTransaction();
mRealm.copyToRealmOrUpdate(new XYZModel(string));
mRealm.commitTransaction();
}
// and so on
I was wondering how can pass this to a single method to do the transaction where i would just pass the string and the model class name,
How can I achieve something like this:
private void copyToRealm(RealmModel model, String string){
mRealm.beginTransaction();
mRealm.copyToRealmOrUpdate(new model(string));
mRealm.commitTransaction();
}
and call it like
copyToRealm(ABCModel, string);
and I want to do the same when querying like
public RealmResults<?> queryChemistry(RealmModel model, String year) {
return realm.where(model.class)
.contains("Title", "string")
.findAll();
}
and by models I mean the class which extends the RealmObject.
if you need any more info lemme know I will update the question, Thanks
This isn't strictly a Realm question to be honest, I personally would just create an enum that knows how to create its corresponding thing and how to evaluate if the model belongs to it.
public enum Models {
ABC {
#Override
public boolean evaluateIfModel(String input) {
return input.contains("abc");
}
#Override
public RealmModel createModel(String input) {
return new ABCModel(input);
}
};
public abstract boolean evaluateIfModel(String input);
public abstract RealmModel createModel(String input);
}
for(Models model : Models.values()) {
if(model.evaluateIfModel(input)) {
final _model = model;
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(_model.createModel(input));
}
});
break;
}
}
public <T extends RealmModel> RealmResults<T> queryChemistry(T model, String year) {
return realm.where(model.getClass())
.contains("Title", "string")
.findAll();
}

Android databinding with Firebase [duplicate]

I have data in my firebase DB, everything works fine until I try to De-serialize the data.
Error: argument 1 has type io.realm.RealmList, got java.util.ArrayList
Here's my code:
DatabaseReference root = FirebaseDatabase.getInstance().
getReferenceFromUrl("https://swing-8792d.firebaseio.com/playlist");
Query playlistQuery = root.orderByKey().equalTo(key);
playlistQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot child : dataSnapshot.getChildren()) {
Log.d("Child", child + "");
Playlist receivedPlaylist = child.getValue(Playlist.class);
Playlist playlist = new Playlist();
playlist.setCreatedBy(receivedPlaylist.getCreatedBy());
playlist.setName(receivedPlaylist.getName());
playlist.setMyMap(receivedPlaylist.getMyMap());
playlist.setQrKey(receivedPlaylist.getQrKey());
playlist.setCount(receivedPlaylist.getCount());
playlist.setId(receivedPlaylist.getId());
playlist.setTracks(receivedPlaylist.getTracks());
mPlaylist.add(playlist);
}
This is my POJO class:
#RealmClass
public class Playlist extends RealmObject {
String name;
Long id;
RealmList<Track> tracks;
Integer count;
String createdBy;
RealmList<UserMap> myMap;
String qrKey;
public RealmList<UserMap> getMyMap() {
return myMap;
}
public void setMyMap(RealmList<UserMap> myMap) {
this.myMap = myMap;
}
public Playlist(){}
public String getQrKey() {
return qrKey;
}
public void setQrKey(String qrKey) {
this.qrKey = qrKey;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public RealmList<Track> getTracks() {
return tracks;
}
public void setTracks(RealmList<Track> tracks) {
this.tracks = tracks;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
}
If I try to de-serialize with Normal POJO class (i.e Removing Realm) it works fine.
Firebase won't work with classes that do not have default constructor or private variables i.e no public getter/setter.
A easier solution in your case would be to make a middleware class that is the same pojo just not extending RealmObject. Next initialise your RealmObject subclass using the values of the pojo.
Pseudo code
class SimplePojoPlaylist {
public String variable;
}
class Playlist extends RealmObject {
public String variable;
}
Then first cast into SimplePojoPlaylist
for (DataSnapshot child : dataSnapshot.getChildren()) {
SimplePojoPlaylist receivedPlaylist = child.getValue(SimplePojoPlaylist.class);
Playlist playList = new Playlist();
playList.variable = receivedPlaylist.variable;
}
RealmList is not a supported type for deserialization. Your database checks its structure and deduces that tracks should be an ArrayList. Then, when it tries to convert it, it finds that the types do not match.
Check this link from the docs:
Also, it is a good practice to make your objects immutable to avoid unwanted access and/or modifications.
Creating an empty object from scratch and then calling setter methods to define its state is not a very good pattern, because it can create a situation where an object is accessed before when its state is "broken".
If you need to create an object that is flexible, has a few mandatory fields and some optional, consider using the Builder pattern, although to do it you'd have to redesign your model.
wikipedia - Builder
If you don't need/want to use a builder, my advice is:
1) Make the empty constructor private and create another public one that requires all the fields.
2) Change your tracks field to be of type "List". Then, if you need the object to return a RealmList create another getter method such as tracksAsRealmList() that makes a RealmList out of the member list and returns it.
3) Make sure that the "Track" model has an empty private constructor, a public one with all of its parameters and that all of its fields are supported by firebase deserialization.
4) Unless strictly necessary, make your object fields private and set its value through a setter method.
I hope this helps you.

Categories

Resources