Improving the INotifiedPropertyChanged implementation

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.104.238