As you may have noticed from previous projects, our standard property implementation for handling property changes looks like the following:
private string _descriptionMessage = "Take a Picture"; public string DescriptionMessage { get { return _descriptionMessage; } set { if (value.Equals(_descriptionMessage)) { return; } _descriptionMessage = value; OnPropertyChanged("DescriptionMessage"); } }
The repeated code in every public property makes our view-model code look much bigger than it actually is. In all your code sheets, a good coding practice to think about is how you can reduce the amount of lines of code and, especially repeated code. The following function SetProperty
is an example of how we can turn 13 lines of code into just two:
protected void SetProperty<T>(string propertyName, ref T referenceProperty, T newProperty) { if (!newProperty.Equals(referenceProperty)) { referenceProperty = newProperty; } OnPropertyChanged(propertyName); }
In all properties, we always check first if the value being assigned is different to the current value before firing the OnPropertyChanged
function. Since this is a generic type function, the same logic can be used for any property on all view-models. Now the DescriptionMessage
property will look like the following:
public string DescriptionMessage { get { return _descriptionMessage; } set { SetProperty(nameof(DescriptionMessage), ref _descriptionMessage, value); } }
Let's add the rest of the ViewModelBase
as follows:
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } protected virtual async Task LoadAsync(IDictionary<string, object> parameters) { } #endregion #region Public Methods public Task<bool> NotifyAlert(string message) { var tcs = new TaskCompletionSource<bool>(); Alert?.Invoke(this, new AlertArgs() { Message = message, Tcs = tcs }); return tcs.Task; } public void OnShow(IDictionary<string, object> parameters) { LoadAsync(parameters).ToObservable().Subscribe( result => { // we can add things to do after we load the view model }, ex => { // we can handle any areas from the load async function }); } #endregion }
The preceding functions are the same from previous implementations. Take note of how we fire the Alert
event. Since we now have access to C# 6.0, we can turn a standard null check on an event like the following:
If (Alert != null) { Alert(this, new AlertArgs() { Message = message, Tcs = tcs }); }
Into this:
Alert?.Invoke(this, new AlertArgs() { Message = message, Tcs = tcs });
It looks much cleaner, meaning we can remove all the if
statements.
Now let's add a new file called MainPageViewModel.cs
and implement the following:
public class MainPageViewModel : ViewModelBase { #region Private Properties private readonly IMethods _methods; private string _descriptionMessage = "Take a Picture"; private string _cameraTitle = "Camera"; private string _exitTitle = "Exit"; private ICommand _cameraCommand; private ICommand _exitCommand; #endregion }
Exactly like the other MainPageViewModel
objects, the MainPage
layout is the same, with two buttons, an image, and a label.
Now let's add the public
properties. We are going to use the new SetProperty
function for each public
property:
#region Public Properties public string DescriptionMessage { get { return _descriptionMessage; } set { SetProperty(nameof(DescriptionMessage), ref _descriptionMessage, value); } } public string CameraTitle { get { return _cameraTitle; } set { SetProperty(nameof(CameraTitle), ref _cameraTitle, value); } } public string ExitTitle { get { return _exitTitle; } set { SetProperty(nameof(ExitTitle), ref _exitTitle, value); } } public ICommand CameraCommand { get { return _cameraCommand; } set { SetProperty(nameof(CameraCommand), ref _cameraCommand, value); } } public ICommand ExitCommand { get { return _exitCommand; } set { SetProperty(nameof(ExitCommand), ref _exitCommand, value); } } #endregion
Now for the constructor, we are going to use the Command
factory again to instantiate our binded Command
:
#region Constructors public MainPageViewModel (INavigationService navigation, Func<Action, ICommand> commandFactory): base (navigation, methods) { _methods = methods; _exitCommand = commandFactory (async () => { await NotifyAlert("GoodBye!!"); _methods.Exit(); }); _cameraCommand = commandFactory (async () => await Navigation.Navigate(PageNames.CameraPage, null)); } #endregion
Now let's build the next view-model for the CameraPage
. Add a new file called CameraPageViewModel.cs
to the ViewModels
folder and implement the private properties to begin with:
public sealed class CameraPageViewModel : ViewModelBase { #region Private Properties private Orientation _pageOrientation; private byte[] _photoData; private string _loadingMessage = "Loading Camera..." private bool _canCapture; private bool _cameraLoading; private bool _isFlashOn; private bool _photoEditOn; #endregion }
The CameraPage
is going to include an Orientation
property for adjusting Grid
rows and columns using converters. The _photoData
property will be used for recording the image taken as bytes, we will also be using these bytes to bind to an ImageSource
. The _loadingMessage
and _cameraLoading
properties are used when displaying a view showing the native camera hardware is busy. The _isFlashOn
will be used to control UI elements displaying the status of the flash. The CameraPage
will also have a target image representing the focus target. Then finally, the _canCapture
is used to determine whether the camera has loaded and we are ready to take photos, and the _photoEditOn
is used to bind the visibility status of a view showing the photo just taken.
Next, we add the public
properties; following are two to get you started:
#region Public Properties public bool CanCapture { get { return _canCapture; } set { SetProperty(nameof(CanCapture), ref _canCapture, value); } } public string LoadingMessage { get { return _loadingMessage; } set { SetProperty(nameof(LoadingMessage), ref _loadingMessage, value); } } #endregion
Add the constructor as follows:
#region Constructors and Destructors public CameraPageViewModel(INavigationService navigation, Func<Action, ICommand> commandFactory) : base (navigation, methods) { } #endregion
Now for the public
functions, we have the AddPhoto
function, this will take the image as bytes from the native side, and the PhotoData
is assigned for the ImageSource
binding:
public void AddPhoto(byte[] data) { PhotoData = data; PhotoEditOn = true; }
We also have a function for resetting the variables used in the current photo taken. When the PhotoEditOn
is false
, this means we remove the view that is displaying the current photo taken. When the PhotoData
property is assigned an empty byte array, this means we have freed the data of the image that is currently displaying:
public void ResetEditPhoto() { PhotoData = new byte[] { }; PhotoEditOn = false; }
Finally, we have two more functions that are called when the page appears and disappears:
public void OnAppear() { CameraLoading = false; } public void OnDisappear() { CameraLoading = true; ResetEditPhoto(); }
The OnAppear
function simply resets the CameraLoading
property to false
, and the OnDisappear
function resets the entire view-model; when we return to this page, the state is the same as the starting point (that is, the camera is not loading, no photo is showing)
Excellent! Now that we have built our view-models, let's add the PortableModule
for our IoC container as follows:
public class PortableModule : IModule { #region Public Methods public void Register(ContainerBuilder builder) { builder.RegisterType<MainPageViewModel> ().SingleInstance(); builder.RegisterType<CameraPageViewModel> ().SingleInstance(); } #endregion }
Let's begin building the user interface screens.
18.119.104.238