Limitations of the Current Approach

0
106

As discussed earlier, Tailspin’s requirements for the synchronization service are relatively simple because the online Tailspin Surveys service does not allow tenants to modify a survey after they have published it. However, it is possible for tenants to delete surveys in the online application. The current synchronization process in the sample application does not take this into account, so the number of survey definitions stored on the client never decreases. Furthermore, the client
will continue to be able to submit answers to surveys that no longer exist in the online service. A real implementation should extend the synchronization logic to accommodate this scenario. One possible solution would be to give every survey an expiration date and make it the mobile client’s responsibility to remove out-of-date surveys. Another solution would be to adopt a full-blown synchronization service, such as the Microsoft Sync Framework.

In addition, the current approach does not address the use case where a user removes a tenant from their list of preferred tenants. The mobile client application will not receive any new surveys from the deselected tenants, but the application does not remove any previously downloaded surveys from tenants who are no longer on the list. A complete synchronization solution for Tailspin should also address this use case.

Inside the Implementation

Now is a good time to walk through the code that implements the data synchronization in more detail. As you go through this section, you may want to download the Visual Studio solution for the Tailspin Surveys application from CodePlex (http://go.microsoft.com/fwlink/?LinkId=205602).

The user can initiate the synchronization process by tapping the Sync button on the SurveyListView page. This sends a command to the SurveyListViewModel view model, which, in turn, starts the synchronization process. While the synchronization process is running, the application displays an indeterminate progress bar because it has no way of telling how long the synchronization process will take to complete. If the synchronization process is successful, the Survey ListViewModel class rebuilds the lists of surveys that are displayed by the SurveyListView page. If the synchronization process fails with a network error or a credentials error, the SurveyListViewModel class does not rebuild the lists of surveys that are displayed by the Survey-ListView page.

The SurveyListViewModel class uses Rx to run the synchronization process asynchronously by invoking the StartSynchronization method in the SurveysSynchronizationService class. When the synchronization process is complete, the SurveysSynchronization Service class returns a summary of the synchronization task as a collection of TaskCompletedSummary objects. The view model updates the UI by using the ObserveOnDispatcher method to run the
code on the dispatcher thread. The following code example shows the StartSync method in the SurveyListViewModel class that interacts with the SurveysSynchronizationService class.

C#
private readonly
ISurveysSynchronizationService synchronizationService;

public void StartSync()
{
if (this.IsSynchronizing)

{
return;
}
this.IsSynchronizing = true;
this.synchronizationService
.StartSynchronization()
.ObserveOnDispatcher()
.Subscribe(
taskSummaries => this.SyncCompleted(taskSummaries));
}

The SurveysSynchronizationService class uses Rx to handle the parallel and asynchronous behavior in the synchronization process. Figure 5 shows the overall structure of the StartSync and Start Synchronization methods and how they use Rx to run the synchronization tasks in parallel.

The synchronization methods

The StartSynchronization method in the SurveysSynchronization Service class uses the Observable.ForkJoin method to define the set of parallel operations that make up the synchronization process. The ForkJoin method blocks until all the parallel operations are complete.

The following code example shows the SurveysSynchronization Service class and includes an outline of the StartSynchronization method that the SurveyListViewModel class calls. This code implements the set of tasks shown in Figure 5.

C#

using Microsoft.Phone.Reactive;

public class SurveysSynchronizationService :
ISurveysSynchronizationService
{

public IObservable<TaskCompletedSummary[]>
StartSynchronization()
{
var surveyStore = this.surveyStoreLocator.GetStore();
var getNewSurveys =
this.surveysServiceClientFactory()
.GetNewSurveys(…
// Get the list of new surveys from Tailspin Surveys.

saveSurveyAnswers = this.surveysServiceClientFactory()
.SaveSurveyAnswers(…
// Save completed surveys to the Tailspin Surveys service.

return Observable.ForkJoin(getNewSurveys, saveSurveyAnswers);
}
}

 

Note: The application uses the Funq dependency injection container to create the  SurveysSynchronizationService instance. For more information, see the ViewModelLocator class.

The StartSynchronization method uses Rx to run the two synchronization tasks asynchronously and in parallel. When each task completes, it returns a summary of what happened in a TaskCompleted Summary object, and when both tasks are complete, the method returns an array of TaskCompletedSummary objects from the ForkJoin method.

The getNewSurveys Task

The getNewSurveys task retrieves a list of new surveys from the Tailspin Surveys service and saves them in isolated storage. When the task is complete, it creates a TaskCompletedSummary object with information about the task. The following code example shows the complete definition of this task that breaks down to the following subtasks:

  • The GetNewSurveys method returns an observable list of SurveyTemplate objects from the Tailspin Surveys service.
  • The Select method saves these surveys to isolated storage on the phone, updates the last synchronization date, and then returns an observable TaskCompletedSummary object.
  • The Catch method traps any errors and returns a TaskCompleted Summary object with details of the error.

C#
var getNewSurveys =
this.surveysServiceClientFactory()
.GetNewSurveys(surveyStore.LastSyncDate)
.Select(
surveys =>
{
surveyStore.SaveSurveyTemplates(surveys);
if (surveys.Count() > 0)
{
surveyStore.LastSyncDate =
surveys.Max(s => s.CreatedOn).ToString(“s”);
}
return new TaskCompletedSummary
{
Task = GetSurveysTask,
Result = TaskSummaryResult.Success,
Context = surveys.Count().ToString()
};
})
.Catch(
(Exception exception) =>
{
if (exception is WebException)
{
var webException = exception as WebException;
var summary = ExceptionHandling.GetSummaryFromWebException(
GetSurveysTask, webException);
return Observable.Return(summary);
}
if (exception is UnauthorizedAccessException)
{
return Observable.Return(new TaskCompletedSummary

{
Task = GetSurveysTask,
Result = TaskSummaryResult.AccessDenied
});
}
throw exception;
});

The saveSurveyAnswersTask

The saveSurveyAnswersTask saves completed survey answers to the Tailspin Surveys service and then removes them from isolated storage to free up storage space on the phone. It returns an observable Task CompletedSummary object with information about the task. The following code example shows the complete definition of this task that breaks down to the following subtasks:

  1. The GetCompleteSurveyAnswers method gets a list of completed surveys from isolated storage.
  2. The first call to Observable.Return creates an observable TaskCompletedSummary object so that the task returns at least one observable object (otherwise, the ForkJoin method may never complete). This also provides a default value to return if there are no survey answers to send to the Tailspin Surveys service.
  3. The SaveSurveyAnswers method saves the completed surveys to the Tailspin Surveys service and returns an
    observable list of saved SurveyModel objects.
  4. The Select method deletes all the saved surveys from isolated storage and then returns an observable TaskCompleted Summary object.
  5. The Catch method traps any errors and returns a Task CompletedSummary object with details of the error.

C#
var surveyAnswers = surveyStore.GetCompleteSurveyAnswers();
var saveSurveyAnswers =
Observable.Return(new TaskCompletedSummary
{
Task = SaveSurveyAnswersTask,
Result = TaskSummaryResult.Success,
Context = 0.ToString()
});
if (surveyAnswers.Count() > 0)

{
saveSurveyAnswers =
this.surveysServiceClientFactory()
.SaveSurveyAnswers(surveyAnswers)
.Select(
unit =>
{
var sentAnswersCount = surveyAnswers.Count();
surveyStore.DeleteSurveyAnswers(surveyAnswers);
return new TaskCompletedSummary
{
Task = SaveSurveyAnswersTask,
Result = TaskSummaryResult.Success,
Context = sentAnswersCount.ToString()
};
})
.Catch(
(Exception exception) =>
{

});
}