Shared Windows 8 and Windows Phone 8 View Model Creation

In my APIMASH Starter Kit for EventbriteAPI, I included the View Models in a Portable Class Library project (APIMASH Eventbrite Core).  As I mentioned in earlier posts, I chose this approach so I could reuse them in both of my apps (Windows 8 and Windows Phone 8). 

 

I also wanted to go a bit further, and with the goal of making the creation of view models for similar projects easier, I included in one of the portable libraries (APIMASH Core library, for more info see this post) reusable base classes that take care of most of the common tasks I went through as I was implementing the code for the creation of view models in my Starter Kit.

 

These tasks are the implementation of the interfaces INotifyPropertyChanged and INotifyDataErrorInfo, as well as providing a way to implement the mapping process from a Data Transfer Object (DTO).

 

INotifyPropertyChanged and INotifyDataErrorInfo

 

If you are a XAML developer you are probably very familiar with these interfaces.  Through INotifyPropertyChanged, the XAML infrastructure “knows” when a property changes in the underlying object that is bound to a control, and therefore update the control accordingly. Similarly, when a change occurs in a bound property of a control (e.g. UI data entry), the XAML infrastructure, through this interface, updates the underlying object (a View Model).

 

The INotifyDataErrorInfo interface provides a way to implement simple error handling and data validation in your View Models. By implementing this interface you can benefit from the controls that have built-in mechanisms to use this interface to determine whether an error has occurred. You can also use it in your application logic to determine the state of a ViewModel and make decisions in your code accordingly. For example you can programmatically validate if the user has entered all the information the application needs before making an API call.

 

The following figure shows the implementation of both of these interfaces in the base class ApiMashBindable included in the APIMASH Core portable library.

 

 

public abstract class ApiMashBindable : INotifyPropertyChanged, INotifyDataErrorInfo
    {
        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
        {
            if (object.Equals(storage, value)) return false;

            if (CheckErrorState<T>(value, propertyName))
                OnErrorStateChanged(propertyName);

            storage = value;
            this.OnPropertyChanged(propertyName);
            return true;
        }

        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var eventHandler = this.PropertyChanged;
            if (eventHandler != null)
            {
                eventHandler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion

        #region INotifyDataErrorInfo
        private Dictionary<string, string> _errors = new Dictionary<string, string>();

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public bool HasErrors
        {
            get { return _errors.Count > 0; }
        }

        protected void OnErrorStateChanged([CallerMemberName] string propertyName = null)
        {
            var eventHandler = this.ErrorsChanged;
            if (eventHandler != null)
            {
                eventHandler(this, new DataErrorsChangedEventArgs(propertyName));
            }
        }

        private bool CheckErrorState<T>(T value, [CallerMemberName] String propertyName = null)
        {
            if (propertyName == null)
                return false;

            var msg = ValidateProperty(value, propertyName);

            if (!string.IsNullOrEmpty(msg))
            {
                if (_errors.ContainsKey(propertyName))
                {
                    _errors[propertyName] = msg;
                }
                else
                {
                    _errors.Add(propertyName, msg);
                };

                return true;
            }


            if (_errors.ContainsKey(propertyName))
            {
                _errors.Remove(propertyName);

                return true;
            }


            return false;

        }

        public System.Collections.IEnumerable GetErrors(string propertyName)
        {
            return _errors;
        }

        public string GetErrorsFlattened(string separator)
        {
            return string.Join(separator, _errors.Values.ToArray<string>());
        }

        #endregion

        #region Virtual Methods
        protected virtual string ValidateProperty(object value, [CallerMemberName] string propertyName = null)
        { return string.Empty; }
        #endregion

    }

 

From the code above, you can see that the implementation of the INotifyPropertyChanged is fairly standard but with the addition that I  check for errors when a new value is set, and if the new value changes the error state of the view model, I raise the event  OnErrorStateChanged from INotifyDataErrorInfo

 

The error state changes when either the new value of the given property is invalid (i.e. a transition to an error state) or or when the value is now valid and the previous one was not (i.e. a transition from an error state to a valid state).

 

You probably have also noticed that the actual validation of the property occurs in ValidateProperty(). 

 

The default implementation of this method will return string.Empty for all properties -i.e. no error. If I  want to implement a validation for a particular property in my view model, I just override ValidateProperty() and based on the property name and the new value, I could perform the necessary validation. 

 

This also brings the important point that in order for this to work, in concrete implementations of this class, when I call the method SetProperty<T> I need to pass the property name.  The following figure shows a sample implementation of a EventbriteAPI Starter Kit View Model.

 

public class EventViewModel : ApiMashBaseViewModel
{
    ...

    private long _id;
    public long Id
    {
        get { return _id; }
        set { this.SetProperty(ref _id, value, "Id"); }
    }

    ...

    protected override string ValidateProperty(object value, string propertyName = null)
    {
        if (propertyName == "Id")
        {
            if (string.IsNullOrEmpty(value.ToString()))
            {
                return "The event Id required.";
            }
        }

        return string.Empty;
    }

    ....    
}

 

 

Loading Data and Mapping

One important feature I wanted to provide, is the availability to control when in the overall lifecycle of the application, the application loads the data. For this, I created a base View Model with one abstract method, LoadDataAsync(). This method could be overridden by a derived class and the applications can control when to load the data in the instances of this class, by calling this method at the appropriate time. 

 

The following figure shows the base class ApiMashBaseViewModel.

 

namespace APIMASH_Core.ViewModels
{
    public abstract class ApiMashBaseViewModel : ApiMashBindable
    {

        #region Properties

        private bool _isDataLoaded = false;
        public bool IsDataLoaded
        {
            get { return _isDataLoaded; }
            protected set {this.SetProperty(ref _isDataLoaded , value, "IsDataLoaded"); }
        }

        private long _id;
        public long Id
        {
            get { return _id; }
            set { this.SetProperty(ref _id, value, "Id"); }
        }

        private string _name;
        public string Name
        {
            get { return _name; }
            set { this.SetProperty(ref _name, value , "Name"); }
        }

        private string _description;
        public string Description
        {
            get { return _description; }
            set { this.SetProperty(ref _description, value , "Description"); }
        }
        #endregion

        #region Abstract Methods

        public abstract Task<bool> LoadDataAsync();
        
        #endregion
    }

}

 

The following demonstrates a class that extends this base class by overriding the LoadDataAsync() method with an API call and mapping logic.  The API call returns a ExpandoObject as the DTO and I map the results to the view model

 

Note that I noticed that some of the properties from the EventbriteAPI response were not available in all the results,

that’s why I check if the property exist before attempting to get it. 

public class EventViewModel : ApiMashBaseViewModel
{
   ...

    #region LoadData Methods

    public async override Task<bool> LoadDataAsync()
    {
        if (this.HasErrors) return false;

        dynamic apiResult = await ApiMashInvoke.InvokeAsync<ExpandoObject>(
                                            ApiMashInvoke.ApiEndPointUriBuilder(ApiResources.EVENT_GET,
                                                new KeyValuePair<string, string>("app_key", ApiResources.APP_KEY),
                                                new KeyValuePair<string, string>("id", this.Id.ToString())));


        this.Locations.Clear();

        LoadDataFromEventDTO(apiResult.@event);

        this.IsDataLoaded = await this.MyLocation.LoadDataAsync();

        return this.IsDataLoaded;

    }

    internal void LoadDataFromEventDTO(dynamic eventDTO)
    {
        var dic = (IDictionary<string, object>)eventDTO;

        this.Id = eventDTO.id;
        this.Name = eventDTO.title;
        this.Description = eventDTO.description;
        this.StartDate = DateTime.Parse(eventDTO.start_date);


        if (dic.ContainsKey("logo"))
            this.LogoUrl = new Uri(eventDTO.logo);

        if (dic.ContainsKey("url"))
            this.EventUrl = new Uri(eventDTO.url);

        if (dic.ContainsKey("category"))
            this.Categories = eventDTO.category;

    }

    #endregion

    ...
}

 

Conclusion

In the blog post, I discussed how I included in a portable library a  base view model that implements the interfaces  INotifyPropertyChanged and INotifyDataErrorInfo  as well as the approach I followed to control how the data is loaded and mapped to the view model.


No Comments