-
Notifications
You must be signed in to change notification settings - Fork 1
Architecture
This document serve to explain some stuff
- Creating A Service
- IoC Container
- ViewModelLocator
- Logging
- Navigation Service
- Login Service
- Data Service
This aims to illustrate the thought process when creating a service.
- What is this service supposed to do
- From a user perspective, what are the functions that it should have
- When attempting to call stuff from another service (e.g. dialog), think whether it should be this service calling it, or the user of this service calling it.
Based on 1 and 2, it should be possible to generate an interface for this Service, and work from there.
-
Should the caller be able to determine if the method runs in the background? Or should it always be in the background?
-
Note that asynchronous methods cannot take parameters by reference
-
How do I make sure each method does what it says it is supposed to do?
- Think from the user perspective
- In the test cases put what is the expected input and what is the expected output
-
What happens if my service deals with UI?
- Try your best to separate things that deal with UI and things that does not
- Test the stuff that does not deal with UI
The IoC container used in this project is AutoFac, selected due to ease of use, as well as good documentation and multiple examples of people using it on their Xamarin project.
To use an IoC container:
- Register types
- Specify lifetime
- Resolve
The ViewModelLocator is to allow design time evaluation of the ViewModel while still allowing constructor injection of the ViewModel.
XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="NottCS.Views.MenuPage"
BindingContext="{Binding Source={StaticResource Locator}, Path=Menu}"
Title="Menu">
ViewModelLocator class:
- Register the type with the container and specify the lifetime
builder.RegisterType<MenuViewModel>().InstancePerDependency();
- Expose the property to be binded
public MenuViewModel Menu => _container.Resolve<MenuViewModel>();
The constructor injection is done through the use of the IoC Container.
The logging is done through the use of NLog.
Main advantages:
- Can specify logging levels
- Easily include timestamp of log
- Easily format log string to include more information like callsite
- Easily change logging medium, i.e. Console, Text file, etc.
NLog setup found under App.xaml.cs under SetupNLog() function. (Updated as of 3 Nov 2018)
Sample usage:
private readonly ILogger<MainViewModel> _logger;
public MainViewModel(ILogger<MainViewModel> logger)
{
_logger = logger;
logger.LogInformation("MainViewModel created");
}
Since this application has been updated to use a drawer style, there are 2 ways for navigation to happen. Page terminologies illustrated here
1. Replace the Detail page. This is needed when an item on the Drawer/Menu Page is selected.
Usage:
await _navigationService.SetDetailPageAsync(viewModelType, navigationParam);
2. Navigate from the current Detail page. This is needed when the Detail page itself needs to navigate further.
Usage:
await _navigationService.NavigateToAsync<ViewModelType>(navigationParam);
The login service uses the MSAL library provided by Microsoft, which is called Microsoft.Identity.Client in NuGet. It is already version 2.3.1 at this time of writing (4 Nov 2018) yet it is still under prerelease.
There is a LoginService wrapper around the MSAL library, due to the complex logic needed to complete the login as well as providing custom logic, i.e. checking domain to be nottingham.edu.my.
Note that an exception will be thrown whenever there is a failure to login, it is up to the UI to handle such exceptions.
On successful login, a token will be returned, which should be used in the RestService for communication with the backend.
Usage:
Basic flow:
BLL: Business Logic Layer, i.e. ClubService, EventService, MemberService, etc.
-
Get:
- ViewModel calls stuff like GetClubList() from BLL
- BLL calls from local db since it is a get
-
Update:
- ViewModel calls stuff like UpdateClub(Club club) from BLL
- BLL checks for internet connectivity (throws if no internet, set application to offline mode)
- BLL updates both local db and backend since it is an update
- ViewModel calls GetClubList() again (note that this only pulls from local, for full remote update need to call sync service)
Note that if Offline Mode is enabled the ViewModel should not be able to call Add/Update/Delete
-
Add: Same as update
-
Delete: Same as update
So basically there are 3 sets of data:
- In memory data used by ViewModels (ObservableCollection)
- Local database data used for caching (SQLite)
- Remote database accessed through endpoint (Django)
-
SyncService.Sync() Synchronise based on time, preferred way of calling, should be called on Login but before Navigation
-
SyncService.FullSync()
Performs full synchronisation, clears local database and pulls everything from remote to update. This should be called when a button somewhere is presseed, probably put the button in SettingsPage?
Currently the Sync() method is using the FullSync() implementation due to lack of backend support. Also need to take care of data race conditions due to multiple access of the Sqlite database (loading data while syncing).
Anything with an image.
Get:
- Backend - URL
- Sqlite - URL
- ViewModel - URL
Add/Update:
- Backend - URL
- Sqlite - URL
- ViewModel - local file path
So basically what happens during an Add/Update is:
- ViewModel to select image
- Path is passed to some ImageService
- ImageService converts the path into a byte[]
- The byte[] is passed into the REST Service and passed to backend
- The resulting URL is then stored into Sqlite
- The next time the image is displayed on ViewModel would be through the URL in Sqlite