Counters custom conflict resolution in realm mobile platform for android - 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

Related

Trying to add relations in Backendless. No error but total count of relations added is always 0

Hi guys I am trying to save objects with relations in Backendless via API. I have two classes namely Task and Reminder. A task can be associated with many reminders hence I want a 1:N relationship between the Task table and Reminder table in Backendless. My Task class is as follows:
public class Task {
public Date created;
public Date updated;
private List<Reminder> reminders = null;
private String ownerId;
#PrimaryKey
#NonNull
private String objectId;
#NonNull
private String taskTitle;
#NonNull
private Date deadline;
#NonNull
private int isCompleted = 0;
#NonNull
private int isExpired = 0;
public String getOwnerId() {
return ownerId;
}
public void setOwnerId(String ownerId) {
this.ownerId = ownerId;
}
#NonNull
public String getObjectId() {
return objectId;
}
public void setObjectId(#NonNull String objectId) {
this.objectId = objectId;
}
public List<Reminder> getReminders() {
return reminders;
}
public void setReminders(List<Reminder> reminders) {
this.reminders = reminders;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getUpdated() {
return updated;
}
public void setUpdated(Date updated) {
this.updated = updated;
}
#NonNull
public int getIsCompleted() {
return isCompleted;
}
public void setIsCompleted(#NonNull int isCompleted) {
this.isCompleted = isCompleted;
}
#NonNull
public int getIsExpired() {
return isExpired;
}
public void setIsExpired(#NonNull int isExpired) {
this.isExpired = isExpired;
}
public String getTaskTitle() {
return taskTitle;
}
public void setTaskTitle(String taskTitle) {
this.taskTitle = taskTitle;
}
public Date getDeadline() {
return deadline;
}
public void setDeadline(Date deadline) {
this.deadline = deadline;
}
}
Reminder Class:
public class Reminder {
private String title;
private Date time;
private String objectId;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
public String getObjectId() {
return objectId;
}
public void setObjectId(String objectId) {
this.objectId = objectId;
}
}
I am saving the objects and setting up the relation as below:
public void saveTaskToServer(final Task task) {
List<Reminder> remindersList = new ArrayList<>();
remindersList = task.getReminders();
final List<Reminder> savedReminders = new ArrayList<>();
if(remindersList!=null && remindersList.size()!=0) {
for
(Reminder reminder : remindersList) {
reminder.setTitle(task.getTaskTitle());
Backendless.Persistence.save(reminder, new AsyncCallback<Reminder>() {
#Override
public void handleResponse(Reminder response) {
savedReminders.add(response);
}
#Override
public void handleFault(BackendlessFault fault) {
Log.i("error saving remidners", fault.toString());
}
});
}
}
Backendless.Persistence.save(task, new AsyncCallback<Task>() {
#Override
public void handleResponse(Task response) {
newTask = response;
Log.i("id is ", newTask.getObjectId());
insertTask(response);
snackbarMessage.postValue("Task Created Successfully.");
}
#Override
public void handleFault(BackendlessFault fault) {
Log.i("error", fault.getMessage());
}
});
Backendless.Persistence.of(Task.class).addRelation(task, "reminders", savedReminders, new AsyncCallback<Integer>() {
#Override
public void handleResponse(Integer response) {
Log.i("response", "added" + response);
newTask.setReminders(savedReminders);
}
#Override
public void handleFault(BackendlessFault fault) {
Log.i("response", "error" + fault.toString());
}
});
}
I have tried saving the relation using the tablename:Class:n instead of the parentColumnName. Also tried saving the objectids of the reminders instead of the reminder objects themselves.The task and reminder objects get saved properly in the backendless console in their respective tables but the reminder column in the Task table still remains empty and no relations get added. Relations count in the backendless call in Android Studio also returns 0. Any advice is really appreciated. I have been following this example.
My relations were not getting saved because I was using the async callbacks in backendless!! I dont know why I didnt see that before. Since the save calls were being made before the async callbacks could finish I was ending up with null values. Fixed it by making the calls synchronous and wrapping them in an async task.

How to update a particular row in realm table android

I have the following table with a PrimaryKey in it. I have inserted some values in the table. Now I need to update a particular value in a particular row. I have a row with gameType as Puzzle and I need to update the currentLevel in the row. But I am not able to achieve that.
GamesDetails table:
public class GamesDetail extends RealmObject {
#PrimaryKey
private String gameType;
private int currentLevel;
private int totalLevel;
private int totalCoins;
private int currentBadge;
public String getGameType() {
return gameType;
}
public void setGameType(String gameType) {
this.gameType = gameType;
}
public int getCurrentLevel() {
return currentLevel;
}
public void setCurrentLevel(int currentLevel) {
this.currentLevel = currentLevel;
}
public int getTotalLevel() {
return totalLevel;
}
public void setTotalLevel(int totalLevel) {
this.totalLevel = totalLevel;
}
public int getTotalCoins() {
return totalCoins;
}
public void setTotalCoins(int totalCoins) {
this.totalCoins = totalCoins;
}
public int getCurrentBadge() {
return currentBadge;
}
public void setCurrentBadge(int currentBadge) {
this.currentBadge = currentBadge;
}
}
Here is what I have tried to update a particular row in the table:
final GamesDetail puzzleGameDetail = realm.where(GamesDetail.class).equalTo("gameType","Puzzle").findFirst();
final int[] nextLevel = {puzzleGameDetail.getCurrentLevel()};
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
puzzleGameDetail.setCurrentLevel(++nextLevel[0]);
realm.copyToRealmOrUpdate(puzzleGameDetail);
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
Log.e(TAG, "Done");
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
Log.e(TAG,error.getMessage());
}
});
But the value is not getting updated and I am getting this following error:
Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
How can I update a particular value in a particular row in the table ?
When calling executeTransactionAsync, the execute block will run in a background thread, any Realm objects access from that thread need to be created/queried on that thread from the Realm instance which is the param of execute.
Move your finding GamesDetail query inside execute block and rest will work fine.

How works delete in realm with relationship?

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();
}
}

Adding a RealmObject to a RealmList in Android

I am currently trying to add a RealmObject to RealmList inside another RealmObject.
So this is the way I am doing it at the moment.
First I create and save a RealmObject called "RouteRealm" like this:
public void insertNewRoute(int routeId, long routeDate) {
realm.beginTransaction();
RouteRealm routeRealm = realm.createObject(RouteRealm.class);
routeRealm.setId(routeId);
routeRealm.setDate(routeDate);
realm.commitTransaction();
}
The class RealmObject looks like this:
public class RouteRealm extends RealmObject {
#PrimaryKey
private int id;
private long date;
private RealmList<RoutePointRealm> routePoints;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public long getDate() {
return date;
}
public void setDate(long date) {
this.date = date;
}
public RealmList<RoutePointRealm> getRoutePoints() {
return routePoints;
}
public void setRoutePoints(RealmList<RoutePointRealm> routePoints) {
this.routePoints = routePoints;
}
}
The above works. The problem occurs when I try to add a RoutePointRealm to the list called routePoints. Here is my code for adding the RoutePointRealm object to the list:
public void insertNewRoutePoint(int routeId, String address, float latitude, float longitude, long routePointId, long routePointTime) {
realm.beginTransaction();
RouteRealm routeRealm = realm.where(RouteRealm.class).equalTo("id", routeId).findFirst();
RoutePointRealm routePointRealm = realm.createObject(RoutePointRealm.class);
routePointRealm.setAddress(address);
routePointRealm.setLatitude(latitude);
routePointRealm.setLongitude(longitude);
routePointRealm.setRoutePointID(routePointId);
routePointRealm.setRoutepointTime(routePointTime);
routeRealm.getRoutePoints().add(routePointRealm);
realm.copyToRealmOrUpdate(routeRealm);
realm.commitTransaction();
}
And the RoutePointRealm looks like this:
public class RoutePointRealm extends RealmObject {
#PrimaryKey
private long routePointID;
private float longitude, latitude;
private long routepointTime;
private String address;
public long getRoutePointID() {
return routePointID;
}
public void setRoutePointID(long routePointID) {
this.routePointID = routePointID;
}
public float getLongitude() {
return longitude;
}
public void setLongitude(float longitude) {
this.longitude = longitude;
}
public float getLatitude() {
return latitude;
}
public void setLatitude(float latitude) {
this.latitude = latitude;
}
public long getRoutepointTime() {
return routepointTime;
}
public void setRoutepointTime(long routepointTime) {
this.routepointTime = routepointTime;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
For some reason, the RoutePointRealm are added to the list, but all of its fields are set to zero and null. I have debugged my app to make sure that all of the parameters contains the correct values with the right datatypes etc. So now I know that the problem is related to the Realm methods.
What am I doing wrong?
First of all, thank you for your answers! I still couldn't get it to work after changing the solutions you've proposed. At least I didn't think so.
The reason I thought it didn't work, was partly because of a mistake that I made with my gui showing a zero value.. This made me to go into debugging the app, but apparently the debugger always shows zero or null values for the Realm objects.. At least in my case.
So at last, I tried making a Toast message with a fetched value from Realm and it returned what it was supposed to.
So I don't think that there were any problems to begin with.. The debugger just got me thinking so. I am sorry if I wasted your time, but I still thought that I should post this as an answer if other were to encounter the same problem.
All your objects are managed by Realm, so you don't need the realm.copyToRealmOrUpdate(routeRealm); call.
the problem comes from ID, Realm not support auto increment behaviour so you should do it manually.
something like :
beginTransaction()
foo.setId(value)
commitTrasaction()
My personal recommendation:
public void insertNewRoute(final int routeId, final long routeDate) {
final RouteRealm routeRealm = new RouteRealm();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
routeRealm.setId(routeId);
routeRealm.setDate(routeDate);
realm.insertOrUpdate(routeRealm);
}
});
}
public void insertNewRoutePoint(final int routeId, final String address, final float latitude,
final float longitude, final long routePointId, final long routePointTime) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RouteRealm routeRealm = realm.where(RouteRealm.class).equalTo(RouteRealmFields.ID, routeId).findFirst();
RoutePointRealm routePointRealm = new RoutePointRealm();
routePointRealm.setAddress(address);
routePointRealm.setLatitude(latitude);
routePointRealm.setLongitude(longitude);
routePointRealm.setRoutePointID(routePointId);
routePointRealm.setRoutepointTime(routePointTime);
routePointRealm = realm.copyToRealmOrUpdate(routePointRealm);
routeRealm.getRoutePoints().add(routePointRealm);
}
});
}

Android Persistent caching: using ModelCache from ignition

I have used ignition in my app to cache my composite object,let say mStudentObject. I have cached my data successfully, the issue is , when i retrieve my object after killing app from recently running apps button(from currently running tasks button) ,i haven't fount any data against key(cached clear automatically).When i re-launch app (with out killing app from recent tasks) object retrieved properly.
i don't know what is wrong with code.I want to cache my object permanently for 2 days. when ever i launch my app,app should get data from cached object either i kill app from currently running tasks or not. Any idea,please share.Here is my complete code:
public class MainActivity extends Activity {
Button[] buttons = null;
// ObjectLRUCache objectLRUCache = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
buttons = new Button[2];
buttons[0] = (Button) findViewById(R.id.button1);// to save data
buttons[1] = (Button) findViewById(R.id.button2); // to get data
// final Student s = new Student("imran", 23, 16);
buttons[0].setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (IgnetionHelper.getInstance()!= null) {
Log.d("test", "key contains, updating");
Student s = new Student("imran", 23, 16);
IgnetionHelper.getInstance().putData(s);
} else{
Log.d(""test),"instance is null..");
}
});
buttons[1].setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
IgnetionHelper ddd = IgnetionHelper.getInstance();
if (IgnetionHelper.getInstance().getData()!= null) {
Student s = (Student) IgnetionHelper.getInstance().getData();
Log.d("test", "key contains, age is: " + s.age);
} else {
Log.d("test", "data is null...");
}
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
and My Person class is as:
public abstract class Person extends CachedModel implements Serializable{
public String name = "";
public int age = 0;
public Person(){};
public Person (String name,int age) {
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Student class is as:
public class Student extends Person{
public String name = "";
public int age = 0;
public int rollNo = 0;
public Student(){
}
public Student(String name, int age, int rollno) {
this.rollNo = rollno;
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getRollNo() {
return rollNo;
}
public void setRollNo(int rollNo) {
this.rollNo = rollNo;
}
#Override
public boolean reloadFromCachedModel(ModelCache modelCache,
CachedModel cachedModel) {
Student student = (Student) cachedModel;
name = student.name;
age = student.age;
rollNo = student.rollNo;
return false;
}
#Override
public String createKey(String id) {
// TODO Auto-generated method stub
return id;
}
}
And finally, ignition helper class is as:
public class IgnetionHelper {
private static final String KEY_FOR_MYOBJECT = "MY_TEST_KEY";
private static ModelCache cache;
private final static int initialCapacity = 1000;
private final static int maxConcurrentThreads = 3;
private final static long expirationInMinutes = 60 * 24 * 2;
private static IgnetionHelper mIgnetionHelper = null;
public static IgnetionHelper getInstance() {
if (cache == null)
cache = new ModelCache(initialCapacity, expirationInMinutes,
maxConcurrentThreads);
if (mIgnetionHelper == null)
mIgnetionHelper = new IgnetionHelper();
return mIgnetionHelper;
}
public boolean putData(CachedModel model) {
model.setId(KEY_FOR_MYOBJECT);
if (model.save(cache)) {
Log.d("IgnetionHelper", "saved.....");
return true;
} else {
Log.d("IgnetionHelper", "saved.....");
return false;
}
// CachedModel model = Feed.find(cache, key, Feed.class);
// if (model != null) {
// Log.d("test", "key contains, updating");
// Feed s = (Feed) model;
// return s.save(cache);
// }
}
public CachedModel getData() {
return Student.find(cache, KEY_FOR_MYOBJECT, Student.class);
}
}
i have found solution of my problem.
Let say you have student object
Student s = new Student("imran",16,23);
and then implement these methods in your ignetionhelper calss:
public static boolean putData(Object object, Context context,String key) {
return GenericStore.saveObject(GenericStore.TYPE_MEMDISKCACHE,key, (Serializable) object, context.getApplicationContext());
}
public static Object getData(Context context,String key) {
return GenericStore.getObject(GenericStore.TYPE_MEMDISKCACHE,key, context.getApplicationContext());
}
https://github.com/wareninja/generic-store-for-android
import library given hare and then use above methods as:
IgnetionHelper.putData(s, context, IgnetionHelper.YOUR_KAY);
Student s=(Student)IgnetionHelper.getData(context,IgnetionHelper.YOUR_KAY);
You will need to use a cache that is stored to disk, when the app is killed the cache you are using appears to be cleared.
There is some information from Google about cache storage here http://developer.android.com/guide/topics/data/data-storage.html and check out this existing SO answer on some options for data caching within your app Best Way to Cache Data in Android

Categories

Resources