Employee Directory Xamarin.Forms

Let me preface this the fact  that when building a mobile app for a large company one of the first things that comes up is the ability to view all their employees in their current intranet.  Especially if they already take pictures of each employee for the intranet, it tends to give them a proxy “Facebook”  feeling.

My instance for this particular client was slightly different, the project manager/lead developer and I had to convince the client this was the best way to step into the mobile app world for his client.  Since he had no idea what we could do or what he wanted to see.  So I set out to begin the mobile app: extending the existing MVC 4 project by adding a new project to the solution strictly for the Web API and creating a new Mobile App project with Xamarin.Forms.

Code coming soon..  After I successfully created an app that displayed all active employees with their picture, the client started wanting different screens of employees.  I attempted to persuade the client in using a filter system rather than having multiple screens (that ultimately toggle a flag or two from the server), but what a client wants is what a client wants.  So I started the second employee directory page, with a difference of only showing “Staff” personnel, then the third page came about for the “Executive” personnel.  This is where I realized that I was duplicating LOTS of code.  So I set out on a refactor mission and something pretty handy came out of it.   Let’s dig into the code…

 public class ActiveEmployeesByCustomerPage : ContentPage, ICarouselLoadingPage
    {
        public bool HasLoadedInitData { get; set; }

        protected ObservableCollection Customers = new ObservableCollection();

        protected ListView listView;

        public ActiveEmployeesByCustomerPage()
        {
            this.Title = "Customers";

            listView = new ListView()
            {
                ItemTemplate = new DataTemplate(typeof(CustomTextCell))
            };
            listView.ItemTemplate.SetBinding(CustomTextCell.TitleProperty, "CustomerName");
            listView.ItemTemplate.SetBinding(CustomTextCell.DetailsProperty, "EmployeeCount");
            listView.ItemsSource = Customers;

            listView.ItemSelected += listView_ItemSelected;

            var stackLayout = new StackLayout()
            {
                Orientation = StackOrientation.Vertical,
                Children = {listView}
            };

            this.Content = stackLayout;
                
        }

        public async Task LoadData()
        {
            if (Customers.Any())
                return;

            var service = new EmployeeStaticsApiService();

            listView.IsRefreshing = true;

            try
            {
                Customers.Clear();

                var items = await service.GetActiveEmployeesByCustomer();

                foreach (var item in items)
                {
                    Customers.Add(new ActiveEmployeesByCustomerViewModel()
                    {
                        CustomerName = item.CustomerName,
                        EmployeeCount = item.EmployeeCount,
                        CustomerId = item.CustomerId
                    });
                }

                listView.IsRefreshing = false;
            }
            catch (Exception ex)
            {
                ...
            }
        }
  }

So let’s talk about what is going on here. It’s a pretty simple implementation of a Page with a Listing of Employees by Customer. The specifics of how we get data here are not the point of this post but how we are building the screen. So you’ll noticed that I’m not using a ViewModel or following any MVVM pattern as you should be to up to par and the was how the screens where being built initially. Arguably this is a screen that could be copied and pasted multiple times to achieve something similar and only having to change the “LoadData()” call.

..
            var service = new EmployeeStaticsApiService();
            Customers.Clear();
            var items = await service.GetActiveEmployeesByCustomer(); 
..

Now it is possible to simply change this after copying and pasting, however, if you needed to add something, lets say a search bar then you’d be going to every single “directory” page and implementing it there. A daunting task to say the least. What if we knew that every “directory” type screen would show the exact same thing but only change where we pull the data? Well, we’d be able to reuse a lot of code and make our lives as developers a dream. For example wouldn’t the following be great to be able to do:

 public class StaffEmployeeDirectoryPage : EmployeeDirectoryPage
    {
        public StaffEmployeeDirectoryPage() 
            :base(new StaffEmployeeViewModel())
        {
            Title = "Staff";
        }
    }

ViewModel:

 public class StaffEmployeeViewModel : EmployeeViewModel
    {
        public StaffEmployeeViewModel()
            : base(App.MenuViewModel.UserId)        {        }

        public override async Task Search()
        {
            IsSearching = true;

            var api = new EmployeeDirectoryApiService();
            try
            {
                var items = await api.GetStaffEmployees(UserId, Skip, SearchTerm, SearchTermCancellationToken.Token);

                foreach (var item in items)
                {
                    MapEmployee(item);
                }

                IsSearching = false;

                InitialSearchComplete = true;
            }
            catch (Exception ex)
            {
                IsSearching = false;

            }
        }
    }

That’s it. Really. Pretty nice huh? Well lets dive deeper and see where the magic is..
What is really going on here is that “StaffEmployeeViewModel” is derived from “EmployeeDirectoryPage”. Since “EmployeeDirectoryPage” is our base implementation of the employee directory we really don’t need to change anything except the title. Our base implementation handles all the controls and loading of data that we need. The real magic comes in the “StaffEmployeeViewModel” which derives from “EmployeeViewModel”. Now “EmployeeViewModel” is the class that does 90% of the work; however, it has one method that is virtual and it is called “Search()”. The reasoning behind this is so that the derived classes my implement their own way of “Searching” for data, so long as “MapEmployee()” is used to populate the ObservableCollection, all will be glorious. Thusly, when I’m ready to make a new “Directory” for some new set of employees I simply derive from “EmployeeViewModel” and “EmployeeDirectoryPage”, respectfully; and then write my overridden Search() method and call it a day.
Below you can see the base class of “EmployeeViewModel”:

 public abstract class EmployeeViewModel : BaseObservableModel
    {
        public bool InitialSearchComplete = false;

        protected int Skip = 0;

        public ObservableCollection SearchResults { get; set; }

        protected CancellationTokenSource SearchTermCancellationToken;

        public EmployeeViewModel(Guid currentUserId)
        {
            IsSearching = false;

            SearchResults = new ObservableCollection();

            SearchTermCancellationToken = new CancellationTokenSource();
        }

        //ADDITIONAL PROPERTIES
        ...

        private string searchTerm;

        public string SearchTerm
        {
            get { return searchTerm; }
            set
            {
                searchTerm = value;

                Skip = 0;

                SearchTermCancellationToken.Cancel();

                SearchResults.Clear();

                if (searchTerm != null)
                {
                    try
                    {
                        Search();
                    }
                    catch (Exception)
                    {
                    }
                }
            }
        }

        ..
        //PROPERTY REMOVAL FOR SHORTENING
        ..

        public virtual async Task Search()
        {
            IsSearching = true;

            var api = new EmployeeDirectoryApiService();
            try
            {
                var items = await api.GetEmployees(UserId, Skip, SearchTerm, SearchTermCancellationToken.Token);

                foreach (var item in items)
                {
                    MapEmployee(item);
                }

                IsSearching = false;

                InitialSearchComplete = true;
            }
            catch (Exception ex)
            {
               ...
            }
        }

        protected void MapEmployee(EmployeeListEntryModel item)
        {
            SearchResults.Add(new EmployeeListEntryViewModel()
            {
                //MAPPING
            });
        }
        ..
    }

Employee Directory Page

	public abstract class EmployeeDirectoryPage : ContentPage, ICarouselLoadingPage
	{
		protected virtual EmployeeViewModel Model { get; set; }

		ContinuousListView listView;

		private ActivityIndicator LoadingDataSpinner;

		protected EmployeeDirectoryPage (EmployeeViewModel model)
		{
			Model = model;

			GeneratePage ();            
		}

		private void GeneratePage ()
		{
			Title = "Employee Directory";

			this.BindingContext = Model;

			LoadingDataSpinner = new ActivityIndicator () {
				BackgroundColor = SharedColor.Green_50
			};
			LoadingDataSpinner.SetBinding (ActivityIndicator.IsVisibleProperty, "IsSearching");
			LoadingDataSpinner.SetBinding (ActivityIndicator.IsRunningProperty, "IsSearching");

			var searchBar = new SearchBar () {
				BindingContext = this.BindingContext,
				BackgroundColor = SharedColor.Green_300,
				Placeholder = "Search:",
			};
			searchBar.SetBinding (SearchBar.IsVisibleProperty, "IsInSearchMode");

			searchBar.SetBinding (SearchBar.TextProperty, "SearchTerm");

			listView = new ContinuousListView () {
				ItemTemplate = new DataTemplate (typeof(ImageCell)) {
					Bindings = {
						{ ImageCell.ImageSourceProperty, new Binding ("Image") },
						{ ImageCell.TextProperty, new Binding ("DisplayName") },
						{ ImageCell.DetailProperty, new Binding ("Department") }
					},
				},
				BackgroundColor = SharedColor.Green_50,
				BindingContext = this.BindingContext,
				HasUnevenRows = false,
				VerticalOptions = LayoutOptions.FillAndExpand
			};
			listView.SetBinding (ListView.ItemsSourceProperty, "SearchResults");

			var stackLayout = new StackLayout () {
				Padding = new Thickness (0, 0, 0, 0),
				Orientation = StackOrientation.Vertical,
				Children = { searchBar,
					LoadingDataSpinner,
					new StackLayout () {
						Padding = new Thickness (5, 0, 5, 0),
						Orientation = StackOrientation.Vertical,
						Children = {
							listView
						},
						BackgroundColor = SharedColor.Green_50,                        
						VerticalOptions = LayoutOptions.FillAndExpand
					}
				},
				BackgroundColor = SharedColor.Green_50
			};

			var tbiSearchButton = new ToolbarItem ("Search", "Search.png", async () => {
				Model.IsInSearchMode = !Model.IsInSearchMode;

			}, ToolbarItemOrder.Primary, 0);

			ToolbarItems.Add (tbiSearchButton);

			this.Content = stackLayout;
		}

//Item_Selected Method
		...


		public bool HasLoadedInitData {
			get {
				return Model.InitialSearchComplete;
			}
			set {
			}
		}

		public async Task LoadData ()
		{
			if (!Model.InitialSearchComplete) {
				await Model.Search ();

				if (!Model.SearchResults.Any ()) {
					//set this.Content to an error message or notification if you'd like.
				}
			}
		}
	}



EDIT 6/21/2016 It was recently pointed out to me that I’ve forgotten to include the BaseObservableModel. The class in itself is pretty simple as it has the INotifiyPropertyChanged interface implemented. I’ve added it below for others who may need it in the endeavors. Till next time!

public class BaseObservableModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(String propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }

        protected bool SetField(ref T field, T value, string propertyName)
        {
            if (EqualityComparer.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }
    }

Leave a Reply

Your email address will not be published. Required fields are marked *