Counters custom conflict resolution in realm mobile platform for android

402 views Asked by At

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.

3

There are 3 answers

0
monajafi On

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

4
ast On

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

```

1
EgorD On

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.