When AppClient modifies objects (data) in the local cache in offline mode a situation might happen when the same objects are modified on the server-side too.
During the synchronization process, AppClient can't perform data modification operations that were made in offline mode automatically because in this case some important changes can be lost and data can be corrupted.
Such a situation is called a conflict.
AppClient offers conflict detection and resolving operations. You, the developer, should take conflict resolution into account as part of app design. We will show you examples of various conflict resolution scenarios to help you.
Let's jump into an example.
There is a collection called Label in the Appery.io database with only one custom field: name (of type String).
A two-way syntonization service was generated and then imported into an app via API Express Extension as a generic service with the name label.
There is only one record (item1) in collection Label and the same record is available (saved) in the app (client). The following tables demonstrate the current client/server state:
Client History |
Client (9:00)ONLINE |
Server |
|
_id |
name |
_updatedAt |
1 |
item1 |
9:00 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1 |
9:00 |
false |
|
🚧
Conflict Situation
A conflict can happen only when an app is offline mode.
The app (AppClient) switches to the offline mode.
The following tables demonstrate the current client/server state:
Client History |
Client (9:00) OFFLINE |
Server |
|
_id |
name |
_updatedAt |
1 |
item1 |
9:00 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1 |
9:00 |
false |
|
Now you are going to see what happens if the client and server are updated to different values when they are disconnected (offline) from the server.
At this point the app (AppClient) updates the record item1 stored in the app to item1_1. This update happens in the app (client).
Client History |
Client (9:00) OFFLINE |
Server |
operation |
_id |
old |
new |
UPDATE |
1 |
item1 |
item1_1 |
|
_id |
name |
_updatedAt |
1 |
item1_1 |
9:00 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1 |
9:00 |
false |
|
On the server, item1 was updated to item1_2 at 9:10 but not from the mobile app but from from external system or an API, for example, an external web application. The following shows the current client/server state:
Client History |
Client (9:00) OFFLINE |
Server |
operation |
_id |
old |
new |
UPDATE |
1 |
item1 |
item1_1 |
|
_id |
name |
_updatedAt |
1 |
item1_1 |
9:00 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_2 |
9:10 |
false |
|
At this point AppClient moves from offline mode to online and starts the synchronization process. AppClient invokes the update operation (item1 > item1_1).
The server verifies the values of _updatedAt and old name from the client with the corresponding values on the server.
Since _updatedAt and old client-side name value (item1) don't equal the current values on server (item1_2), the server returns error response with correspondance error message and server value of the name field.
AppClient moves into the SYNC-FAILED state.
The following shows the current client/server state after the synchronization failed.
Client History |
Client (9:00) SYNC FAILED |
Server |
operation |
_id |
old |
new |
UPDATE |
1 |
item1 |
item1_1 |
|
_id |
name |
_updatedAt |
1 |
item1_1 |
9:00 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_2 |
9:10 |
false |
|
AppClient returns a conflict object which contains all required information to solve a conflict:
- operation: UPDATE.
- old name value: item1.
- new name value: item1_1.
- server name value: item1_2.
A conflict can be handled in the following ways:
- Conflict removal.
- Conflict update.
- Reverting changes.
Let's see an example of each approach.
AppClient removes the conflict (removing the top record from Client History) and restores the old value (item1).
Client History |
Client (9:00) SYNC FAILED |
Server |
|
_id |
name |
_updatedAt |
1 |
item1 |
9:00 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_2 |
9:10 |
false |
|
After that AppClient reruns synchronization process and since Client History is now empty, AppClient switches into the online state.
Client History |
Client (9:00) ONLNE |
Server |
|
_id |
name |
_updatedAt |
1 |
item1 |
9:00 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_2 |
9:10 |
false |
|
AppClient invokes the FETCH operation, the data* on the server, and the client-side is synchronized and LastSyncDate is set to 9:10.
The following shows the client/server state:
Client History |
Client (9:10) ONLNE |
Server |
|
_id |
name |
_updatedAt |
1 |
item1_2 |
9:10 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_2 |
9:10 |
false |
|
This is one way to handle a conflict. Now let's look at another option.
Now let's consider the app is back at the conflict state from the example above. This is the client/server state:
Client History |
Client (9:00) SYNC FAILED |
Server |
operation |
_id |
old |
new |
UPDATE |
1 |
item1 |
item1_1 |
|
_id |
name |
_updatedAt |
1 |
item1_1 |
9:00 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_2 |
9:10 |
false |
|
AppClient updates the conflict object (update the top record from Client History to item1_3). The client data also will be updated. This is how it looks like:
Client History |
Client (9:00) SYNC FAILED |
Server |
operation |
_id |
old |
new |
UPDATE |
1 |
item1 |
item1_3 |
|
_id |
name |
_updatedAt |
1 |
item1_3 |
9:00 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_2 |
9:10 |
false |
|
Now AppClient returns the SYNC operation and performs the UPDATE operation again:
Client History |
Client (9:20) ONLINE |
Server |
|
_id |
name |
_updatedAt |
1 |
item1_3 |
9:20 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_3 |
9:20 |
false |
|
The server considers the client value as newer and updates the data in the database.
AppClient switches to the online mode.
🚧
Invoking Retry Sync
If server value will be updated before invoking retry sync operation then server will be consider this situation again as a conflict. The user will have to resolve the conflict again and rerun the synchronization process.
Now AppClient switches to the offline mode.
Client History |
Client (9:20) OFFLINE |
Server |
|
_id |
name |
_updatedAt |
1 |
item1_3 |
9:20 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_3 |
9:20 |
false |
|
AppClient creates a new object with the name: item2.
Client History |
Client (9:20) OFFLINE |
Server |
operation |
_id |
old |
new |
CREATE |
t_1 |
- |
item2 |
|
_id |
name |
_updatedAt |
1 |
item1_3 |
9:20 |
t_1 |
item2 |
- |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_3 |
9:20 |
false |
|
AppClient updates item1_3 to item1_4.
Client History |
Client (9:20) OFFLINE |
Server |
operation |
_id |
old |
new |
CREATE |
t_1 |
- |
item2 |
UPDATE |
1 |
item1_3 |
item1_4 |
|
_id |
name |
_updatedAt |
1 |
item1_4 |
9:20 |
t_1 |
item2 |
- |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_3 |
9:20 |
false |
|
AppClient removes item2.
Client History |
Client (9:20) OFFLINE |
Server |
operation |
_id |
old |
new |
CREATE |
t_1 |
- |
item2 |
UPDATE |
1 |
item1_3 |
item1_4 |
DELETE |
t_1 |
item2 |
- |
|
_id |
name |
_updatedAt |
1 |
item1_4 |
9:20 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_3 |
9:20 |
false |
|
Another option is to revert changes.
AppClient reverts all local (client) changes. All changes will be restored starting with the most recent one.
The following shows the client/server state when reverting the DELETE operation:
Client History |
Client (9:20) OFFLINE |
Server |
operation |
_id |
old |
new |
CREATE |
t_1 |
- |
item2 |
UPDATE |
1 |
item1_3 |
item1_4 |
|
_id |
name |
_updatedAt |
1 |
item1_4 |
9:20 |
t_1 |
item2 |
- |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_3 |
9:20 |
false |
|
The following shows the client/server state when reverting the UPDATE operation:
Client History |
Client (9:20) OFFLINE |
Server |
operation |
_id |
old |
new |
CREATE |
t_1 |
- |
item2 |
|
_id |
name |
_updatedAt |
1 |
item1_3 |
9:20 |
t_1 |
item2 |
- |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_3 |
9:20 |
false |
|
The following shows the client/server state when reverting the CREATE operation:
Client History |
Client (9:20) OFFLINE |
Server |
|
_id |
name |
_updatedAt |
1 |
item1_3 |
9:20 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_3 |
9:20 |
false |
|
Client History is empty and all local changes were reverted:
Client History |
Client (9:20) ONLINE |
Server |
|
_id |
name |
_updatedAt |
1 |
item1_3 |
9:20 |
|
_id |
name |
_updatedAt |
deleted |
1 |
item1_3 |
9:20 |
false |
|
AppClient can invoke a reset operation and delete all information from Client History and from Client Database.