So far all the Mobile Service operations, including holding the instance of the MobileServiceClient, has been done by the MainViewModel. Clearly as the application grows this is not a viable solution so we need some application services which can be used to hold the reference to the MobileServiceClient and to facilitate application logic such as data access and synchronisation. To this end I’m going to create two services, IDataService and ISyncService with their corresponding implementations as follows:
public interface IDataService
{
IMobileServiceClient MobileService { get; }Task Initialize(string aadAccessToken);
}public class DataService: IDataService
{
private readonly MobileServiceClient mobileService = new MobileServiceClient(
Constants.MobileServiceRootUri,
“wpxaIplpeXtkn——QEBcg12”,
new MobileServiceHttpHandler()
);public IMobileServiceClient MobileService
{
get { return mobileService; }
}public async Task Initialize(string aadAccessToken)
{
var jobj = new JObject();
jobj[“access_token”] = aadAccessToken;
var access = await MobileService.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, jobj);
Debug.WriteLine(access != null);
var data = new MobileServiceSQLiteStore(“inspections.db”);
data.DefineTable<RealEstateProperty>();
data.DefineTable<Inspection>();await MobileService.SyncContext.InitializeAsync(data, new CustomMobileServiceSyncHandler());
}
}
The IDataService implementation holds the reference to the IMoblieServiceClient. This will need to be initialized by passing in the Azure Active Directory access token but there after the MobileService accessor can be used to access data directly through the IMobileServiceClient instance.
public interface ISyncService
{
event EventHandler<DualParameterEventArgs<double, string>> Progress;
Task Synchronise(bool waitForCompletion);
Task ForceUpload();
}public class SyncService: ISyncService
{
[Flags]
private enum SyncStages
{
None = 0,
UploadChanges = 1,
PullProperties = 2,
PullInspections = 4,
All = UploadChanges | PullProperties | PullInspections
}public event EventHandler<DualParameterEventArgs<double,string>> Progress;
public IDataService DataService { get; set; }
private ISynchronizationContext<SyncStages> SynchronizationManager { get; set; }
public SyncService(IDataService dataService)
{
DataService = dataService;
SynchronizationManager = new SynchronizationContext<SyncStages>();
SynchronizationManager.DefineSynchronizationStep(SyncStages.UploadChanges, UploadPendingLocalChanges);
SynchronizationManager.DefineSynchronizationStep(SyncStages.PullProperties, DownloadChangesToRealEstateProperties);
SynchronizationManager.DefineSynchronizationStep(SyncStages.PullInspections, DownloadChangesToInspections);
SynchronizationManager.SynchronizationChanged += SynchronizationManager_SynchronizationProgressChanged;
}public async Task Synchronise(bool waitForCompletion)
{
await SynchronizationManager.Synchronize(SyncStages.All, waitForSynchronizationToComplete: waitForCompletion);
}public async Task ForceUpload()
{
await SynchronizationManager.Synchronize(SyncStages.UploadChanges, true, true);
}private void SynchronizationManager_SynchronizationProgressChanged(object sender, SynchronizationEventArgs<SyncStages> e)
{
var message = e.ToString();
if (Progress != null)
{
Progress(this,new object[]{ e.PercentageComplete, message});
}
}private async Task<bool> UploadPendingLocalChanges(ISynchronizationStage<SyncStages> stage)
{
await DataService.MobileService.SyncContext.PushAsync(stage.CancellationToken);
return true;
}
private async Task<bool> DownloadChangesToRealEstateProperties(ISynchronizationStage<SyncStages> stage)
{
await DataService.MobileService.PullLatestAsync<RealEstateProperty>(stage.CancellationToken);
return true;
}
private async Task<bool> DownloadChangesToInspections(ISynchronizationStage<SyncStages> stage)
{
await DataService.MobileService.PullLatestAsync<Inspection>(stage.CancellationToken);
return true;
}
}
The ISyncService defines the actual synchronization steps. Rather than simply exposing a generic Synchronize method that accepts the a SyncStages parameter to determine which steps are synchronized, the ISyncService actually exposes high level methods for performing a full synchronize (Synchronize) and just to upload pending changes (ForceUpload). Note that the former has a parameter indicating whether the method should wait synchronization completion before returning, whereas the latter will always wait for the upload part of the synchronize to complete.
To make these services available to the view models of the application the BaseViewModel has been updated to include properties for both services:
public class BaseViewModel : INotifyPropertyChanged
{
public IDataService DataService { get; set; }
public ISyncService SyncService { get; set; }
And of course the ViewModelLocator is updated to create instances of these services and assign them to the view model when they’re created:
public class ViewModelLocator
{
public IDataService DataService { get; set; }
public ISyncService SyncService { get; set; }public ViewModelLocator()
{
DataService=new DataService();
SyncService=new SyncService(DataService);
}public MainViewModel Main
{
get { return CreateViewModel<MainViewModel>(); }
}private readonly Dictionary<Type, object> viewModels = new Dictionary<Type, object>();
private T CreateViewModel<T>() where T:new()
{
var type = typeof (T);
object existing;
if (!viewModels.TryGetValue(type, out existing))
{
existing = new T();
viewModels[type] = existing;
}var baseVM = existing as BaseViewModel;
if (baseVM != null)
{
baseVM.DataService = DataService;
baseVM.SyncService = SyncService;
}return (T)existing;
}
}