[AZURE.INCLUDE app-service-mobile-selector-offline-preview]
This tutorial covers the offline sync feature of Mobile Apps for iOS. Offline sync allows end-users to interact with a mobile app--viewing, adding, or modifying data--even when there is no network connection. Changes are stored in a local database; once the device is back online, these changes are synced with the remote service.
Offline sync has several potential uses:
- Improve app responsiveness by caching server data locally on the device
- Make apps resilient against intermittent network connectivity
- Allow end-users to create and modify data even when there is no network access, supporting scenarios with little or no connectivity
- Sync data across multiple devices and detect conflicts when the same record is modified by two devices
If this is your first experience with Mobile Apps, first complete the tutorial Create a Xamarin iOS app.
This tutorial requires the following:
- Visual Studio 2013
- Visual Studio Xamarin extension or Xamarin Studio on OS X
Mobile App offline sync allows end users to interact with a local database when the network is not accessible. To use these features in your app, you initialize MobileServiceClient.SyncContext
to a local store. Then reference your table through the IMobileServiceSyncTable
interface.
This section walks through the offline sync related code in QSTodoService.cs
.
-
In Visual Studio, open the project that you completed in the [Get Started with Mobile Apps] tutorial. Open the file
QSTodoService.cs
. -
Notice the type of the member
todoTable
isIMobileServiceSyncTable
. Offline sync uses this sync table interface instead ofIMobileServiceTable
. When a sync table is used, all operations go to the local store and are only synchronized with the remote service with explicit push and pull operations.To get a reference to a sync table, the method
GetSyncTable()
is used. To remove the offline sync functionality, you would instead useGetTable()
. -
Before any table operations can be performed, the local store must be initialized. This is done in the
InitializeStoreAsync
method:public async Task InitializeStoreAsync() { var store = new MobileServiceSQLiteStore(localDbPath); store.DefineTable<ToDoItem>(); // Uses the default conflict handler, which fails on conflict await client.SyncContext.InitializeAsync(store); }
This creates a local store using the class
MobileServiceSQLiteStore
, which is provided in the Mobile App SDK. You can also a provide a different local store implementation by implementingIMobileServiceLocalStore
.The
DefineTable
method creates a table in the local store that matches the fields in the provided type,ToDoItem
in this case. The type doesn't have to include all of the columns that are in the remote database--it is possible to store just a subset of columns.
-
The method
SyncAsync
triggers the actual sync operation:public async Task SyncAsync() { try { await client.SyncContext.PushAsync(); await todoTable.PullAsync("allTodoItems", todoTable.CreateQuery()); // query ID is used for incremental sync } catch (MobileServiceInvalidOperationException e) { Console.Error.WriteLine(@"Sync Failed: {0}", e.Message); } }
First, there is a call to
IMobileServiceSyncContext.PushAsync()
. This method is a member ofIMobileServicesSyncContext
instead of the sync table because it will push changes across all tables. Only records that have been modified in some way locally (through CUD operations) will be sent to the server.Next, the method calls
IMobileServiceSyncTable.PullAsync()
to pull data from a table on the server to the app. Note that if there are any changes pending in the sync context, a pull always issues a push first. This is to ensure all tables in the local store along with relationships are consistent. In this case, we have called push explicitly.In this example, we retrieve all records in the remote
TodoItem
table, but it is also possible to filter records by passing a query. The first parameter toPullAsync()
is a query ID that is used for incremental sync, which uses theUpdatedAt
timestamp to get only those records modified since the last sync. The query ID should be a descriptive string that is unique for each logical query in your app. To opt-out of incremental sync, passnull
as the query ID. This will retrieve all records on each pull operation, which is potentially inefficient.
-
In the class
QSTodoService
, the methodSyncAsync()
is called after the operations that modify data,InsertTodoItemAsync()
andCompleteItemAsync
. It is also called fromRefreshDataAsync()
, so that the user gets the latest data whenever they perform the refresh gesture. The app also performs a sync on launch, sinceQSTodoListViewController.ViewDidLoad()
callsRefreshDataAsync()
.Because
SyncAsync()
is called whenever data is modified, this app assumes that the user is online whenever they are editing data. In the next section, we will update the app so that users can edit even when they are offline.
In this section, you will modify the app so that it does not sync on app launch or on the insert and update operations, but only when the refresh gesture is performed. Then, you will break the app connection with the mobile backend to simulate an offline scenario. When you add data items, they will be held in the local store, but not immediately synced to the mobile backend data store.
-
Open
QSTodoService.cs
. Comment out the calls toSyncAsync()
in the following methods:InsertTodoItemAsync
CompleteItemAsync
RefreshAsync
Now,
RefreshAsync()
will only load data from the local store, but will not connect to the app backend. -
In
QSTodoService.cs
, change the definition ofapplicationURL
to point to an invalid mobile app URI:const string applicationURL = @"https://your-service.azurewebsites.xxx/"; // invalid URI
-
To ensure that data is synchronized when the refresh gesture is performed, edit the method
QSTodoListViewController.RefreshAsync()
. Add a call toSyncAsync()
before the call toRefreshDataAsync()
:private async Task RefreshAsync () { RefreshControl.BeginRefreshing (); await todoService.SyncAsync(); await todoService.RefreshDataAsync (); // add this line RefreshControl.EndRefreshing (); TableView.ReloadData (); }
-
Build and run the app. Add some new todo items. These new items exist only in the local store until they can be pushed to the mobile backend. The client app behaves as if is connected to the backend, supporting all create, read, update, delete (CRUD) operations.
-
Close the app and restart it to verify that the new items you created are persisted to the local store.
In this section you will reconnect the app to the mobile backend, which simulates the app coming back to an online state. When you perform the refresh gesture, data will be synced to your mobile backend.
-
Open
QSTodoService.cs
. Remove the invalid mobile app URL and add back the correct URL and app key. -
Rebuild and run the app. Notice that the data has not changed, even though the app is now connected to the mobile backend. This is because this app always uses the
IMobileServiceSyncTable
that is pointed to the local store. -
Connect to your backend SQL database to view the data that has been stored. In Visual Studio go to Server Explorer -> Azure -> SQL Databases. Right click your database and select Open in SQL Server Object Explorer.
Notice the data has not been synchronized between the database and the local store.
-
In the app, perform the refresh gesture by pulling down the list of items. This causes the app to call
RefreshDataAsync()
, which in turn callsSyncAsync()
. This will perform the push and pull operations, first sending the local store items to the mobile backend, then retrieving new data from the backend. -
Refresh your database view and confirm that changes have been synchronized.