{"id":21857548,"url":"https://github.com/hjerpbakk/xamarin-forms-birthday","last_synced_at":"2025-04-14T18:32:53.190Z","repository":{"id":149285049,"uuid":"168926423","full_name":"hjerpbakk/xamarin-forms-birthday","owner":"hjerpbakk","description":"Never miss a birthday again! This educational app was created as part of a Xamarin Forms workshop.","archived":false,"fork":false,"pushed_at":"2019-04-08T14:42:33.000Z","size":9697,"stargazers_count":7,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T07:01:31.771Z","etag":null,"topics":["csharp","tutorial","xamarin","xamarin-android","xamarin-forms","xamarin-ios","xaml"],"latest_commit_sha":null,"homepage":"https://hjerpbakk.com/blog/2019/02/07/cross-platform-mobile-development-using-csharp-and-xamarin","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hjerpbakk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2019-02-03T08:52:04.000Z","updated_at":"2023-10-14T14:48:47.000Z","dependencies_parsed_at":null,"dependency_job_id":"aa62ef1a-4b05-4880-b800-4c9ffc8e27e9","html_url":"https://github.com/hjerpbakk/xamarin-forms-birthday","commit_stats":null,"previous_names":["hjerpbakk/xamarin-forms-birthday"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hjerpbakk%2Fxamarin-forms-birthday","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hjerpbakk%2Fxamarin-forms-birthday/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hjerpbakk%2Fxamarin-forms-birthday/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hjerpbakk%2Fxamarin-forms-birthday/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hjerpbakk","download_url":"https://codeload.github.com/hjerpbakk/xamarin-forms-birthday/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248936667,"owners_count":21186079,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","tutorial","xamarin","xamarin-android","xamarin-forms","xamarin-ios","xaml"],"created_at":"2024-11-28T02:29:54.720Z","updated_at":"2025-04-14T18:32:53.165Z","avatar_url":"https://github.com/hjerpbakk.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Xamarin Forms Birthday\n\nNever miss a birthday again! This educational app was created as part of a Xamarin Forms workshop. \n\nhttps://hjerpbakk.com/blog/2019/02/07/cross-platform-mobile-development-using-csharp-and-xamarin\n\n## Show mock-up of the app to be created\n\n![](mockup.png)\n\n### Key takeaway\n\nAlways mock-up your app before starting to code to avoid unecessary coding.\n\n## Create new solution\n\nUse `Blank Forms App` template. Update packages, build and run, it should work.\n\n### Key takeaway\n\nKnow the difference between the different projects and understand how this can run on your phone.\n\n## Navigation and Tab bar\n\nDelete the `MainPage`.\n\nCreate a new folder `Views`.\n\nAdd a new `MainPage` with XAML, inheriting from `ContenPage`. Change the inheritance to `TabbedPage`. The XAML should look like this:\n\n```XAML\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cTabbedPage xmlns=\"http://xamarin.com/schemas/2014/forms\" xmlns:x=\"http://schemas.microsoft.com/winfx/2009/xaml\" xmlns:views=\"clr-namespace:Birthdays.Views\" x:Class=\"Birthdays.Views.MainPage\"\u003e\n    \u003cTabbedPage.Children\u003e\n        \u003cNavigationPage Title=\"Birthdays\"\u003e\n            \u003cx:Arguments\u003e\n                \u003cviews:BirthdaysPage /\u003e\n            \u003c/x:Arguments\u003e\n        \u003c/NavigationPage\u003e\n        \u003cNavigationPage Title=\"Settings\"\u003e\n            \u003cx:Arguments\u003e\n                \u003cviews:SettingsPage /\u003e\n            \u003c/x:Arguments\u003e\n        \u003c/NavigationPage\u003e\n    \u003c/TabbedPage.Children\u003e\n\u003c/TabbedPage\u003e\n```\n\nThree problems now: \n\n1. MainPage has a new namespace\n2. `BirthdaysPage` doesn't exist\n3. `SettingsPage`doesn't exist\n\nTo fix this: \n\n1. Add `using Birthdays.Views;` to `App.xaml.cs`.\n2. Create a `ContentPage` with XAML called `BirthdaysPage`.\n3. Create a `ContentPage` with XAML called `SettingsPage`.\n\nBuild the app and run it.\n\n### Key takeaway\n\nCreating a skeleton of the app first makes it easier to add inn details later. XAML is used to describe the user interface in Xamarin Forms.\n\n## Add titles to the new pages\n\nSet the property `Title` of the two new `ContentPages`.\n\n### Key takeaway\n\nNavigation bars inherit the title of their active child. Each tab has its own navigation stack.\n\n## Create a Grid for the Birthdays page\n\nCreate a simple grid consisting of two eqaul vertical parts like this:\n\n```XAML\n\u003cContentPage.Content\u003e\n    \u003cGrid\u003e\n        \u003cGrid.RowDefinitions\u003e\n            \u003cRowDefinition Height=\"*\" /\u003e\n            \u003cRowDefinition Height=\"*\" /\u003e\n        \u003c/Grid.RowDefinitions\u003e\n        \u003cContentView BackgroundColor=\"Red\" /\u003e\n        \u003cContentView Grid.Row=\"1\" BackgroundColor=\"Green\" /\u003e\n    \u003c/Grid\u003e\n\u003c/ContentPage.Content\u003e\n```\n\n### Key takeaway\n\nGrid is an excellent way to lay out a small number of items on the screen. Verify your design using simple techniques before proceeding with details.\n\n## Create view for closest birthday\n\nCreate a `ContentView` with XAML which will contain the closest birthday, `ClosestBirthdayView`:\n\n```XAML\n\u003cContentView.Content\u003e\n    \u003cStackLayout\u003e\n        \u003cLabel HorizontalTextAlignment=\"Center\" FontSize=\"24\" VerticalOptions=\"Start\" Margin=\"0,30,0,0\" Text=\"Birthday Boi\" /\u003e\n        \u003cLabel HorizontalTextAlignment=\"Center\" FontSize=\"32\" VerticalOptions=\"CenterAndExpand\" Text=\"35 years\" /\u003e\n        \u003cLabel HorizontalTextAlignment=\"Center\" FontSize=\"24\" VerticalOptions=\"End\" Margin=\"0,0,0,30\" Text=\"6.2.1984\" /\u003e\n    \u003c/StackLayout\u003e\n\u003c/ContentView.Content\u003e\n```\n\n```XAML\nxmlns:views=\"clr-namespace:Birthdays.Views\"\n...\n\u003cviews:ClosestBirthdayView /\u003e\n```\n\n### Key takeway\n\nCreate small components to build up your UI. These must be referenced in XAML using the correct namespace. Know and utilize different layout options.\n\n## Create all birthdays view\n\nCreate a `ContentView` with XAML which will show a list of all birthdays, `BirthdaysView`:\n\n```XAML\n\u003cContentView xmlns=\"http://xamarin.com/schemas/2014/forms\" xmlns:x=\"http://schemas.microsoft.com/winfx/2009/xaml\" x:Class=\"Birthdays.Views.BirthdaysView\"\u003e\n    \u003cContentView.Content\u003e\n        \u003cListView\u003e\n            \u003cListView.ItemsSource\u003e\n                \u003cx:Array Type=\"{x:Type x:String}\"\u003e\n                    \u003cx:String\u003ePaul\u003c/x:String\u003e\n                    \u003cx:String\u003eLeto\u003c/x:String\u003e\n                    \u003cx:String\u003eVladimir\u003c/x:String\u003e\n                    \u003cx:String\u003eJessica\u003c/x:String\u003e\n                    \u003cx:String\u003eDuncan\u003c/x:String\u003e\n                \u003c/x:Array\u003e\n            \u003c/ListView.ItemsSource\u003e\n            \u003cListView.ItemTemplate\u003e\n                \u003cDataTemplate\u003e\n                    \u003cTextCell Text=\"{Binding}\" Detail=\"Bursdag\" /\u003e\n                \u003c/DataTemplate\u003e\n            \u003c/ListView.ItemTemplate\u003e\n        \u003c/ListView\u003e\n    \u003c/ContentView.Content\u003e\n\u003c/ContentView\u003e\n```\n\n### Key takeaway\n\nA `ListView` is useful to show lists. `DataTemplates` defines the UI of items in lists or tables. `Bindings` sets vales in the UI based on other data.\n\n## A short stylistic detour\n\nReplace `Application.Resources` in `App.xaml` with:\n\n```XAML\n\u003cApplication.Resources\u003e\n    \u003cResourceDictionary\u003e\n        \u003cStyle TargetType=\"NavigationPage\"\u003e\n            \u003cSetter Property=\"BarBackgroundColor\" Value=\"#F9F9F9\" /\u003e\n        \u003c/Style\u003e\n    \u003c/ResourceDictionary\u003e\n\u003c/Application.Resources\u003e\n```\n\nThis will make the navigation bar be less ugly, at least on iOS.\n\n### Key takeaway\n\nGlobal styles are necessary and can be specified in `App.xaml`.\n\n## Use MVVM and DataBinding to populate the UI\n\nAdd a `Models` folder and the class `Person`:\n\n```csharp\nusing System;\n\nnamespace Birthdays.Models {\n    public class Person {\n        public Person(string name, DateTime date) {\n            Name = name;\n            Birthday = date.ToShortDateString();\n            Age = GetAge(date);\n        }\n\n        public string Name { get; }\n        public string Birthday { get; }\n        public uint Age { get; }\n\n        static uint GetAge(DateTime birthDate) {\n            var now = DateTime.Now;\n            int age = now.Year - birthDate.Year;\n            if (now.Month \u003c birthDate.Month || (now.Month == birthDate.Month \u0026\u0026 now.Day \u003c birthDate.Day)) {\n                age--;\n            }\n\n            return (uint)age;\n        }\n    }\n}\n\n```\n\nAdd a `ViewModels` folder and the class `BirthdaysViewModel`:\n\n```csharp\nusing System;\nusing Birthdays.Models;\n\nnamespace Birthdays.ViewModels {\n    public class BirthdaysViewModel {\n        public BirthdaysViewModel() {\n            ClosestBirthDay = new Person(\"Birthday Boi\", new DateTime(1984, 2, 6));\n        }\n\n        public Person ClosestBirthDay { get; }\n    }\n}\n```\n\nUpdate `ClosestBirthdayView` to bind to a people object:\n\n```xaml\n\u003cLabel Text=\"{Binding ClosestBirthDay.Name}\" ...\n\u003cLabel Text=\"{Binding ClosestBirthDay.Age}\" ...\n\u003cLabel Text=\"{Binding ClosestBirthDay.Birthday}\" ...\n```\n\nAnd set the `BindingContext` of `BirthdaysPage.xaml.cs`:\n\n```csharp\nusing Birthdays.ViewModels;\nusing Xamarin.Forms;\n\nnamespace Birthdays.Views {\n    public partial class BirthdaysPage : ContentPage {\n        public BirthdaysPage() {\n            InitializeComponent();\n            BindingContext = new BirthdaysViewModel();\n        }\n    }\n}\n```\n\nRun the app and success means it looks *kind-of the same*!\n\n### Key takeaway\n\n**MVVM** and **data binding** are the two most important aspects of any Xamarin Forms app. They enable decoupling, easy state managment and makes the app testable! If you remember only two things, remember these patterns.\n\n## Bind the list too\n\nAdd an `ObserveableCollection` to `BirthdaysViewModel` and populate it with dummy data:\n\n```csharp\nusing System;\nusing System.Collections.ObjectModel;\nusing Birthdays.Models;\n\nnamespace Birthdays.ViewModels {\n    public class BirthdaysViewModel {\n        public BirthdaysViewModel() {\n            ClosestBirthDay = new Person(\"Birthday Boi\", new DateTime(1984, 2, 6));\n            FutureBirthdays = new ObservableCollection\u003cPerson\u003e {\n                new Person(\"Paul\", new DateTime(1983, 3, 7)),\n                new Person(\"Leto\", new DateTime(1982, 4, 8)),\n                new Person(\"Vladimir\", new DateTime(1981, 5, 9)),\n                new Person(\"Jessica\", new DateTime(1980, 6, 10)),\n                new Person(\"Duncan\", new DateTime(1979, 7, 11))\n            };\n        }\n\n        public Person ClosestBirthDay { get; }\n        public ObservableCollection\u003cPerson\u003e FutureBirthdays { get; }\n    }\n}\n```\n\nUpdate the `BirthdaysView` to use bindings fitting the ViewModel:\n\n```xaml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cContentView xmlns=\"http://xamarin.com/schemas/2014/forms\" xmlns:x=\"http://schemas.microsoft.com/winfx/2009/xaml\" x:Class=\"Birthdays.Views.BirthdaysView\"\u003e\n    \u003cContentView.Content\u003e\n        \u003cListView ItemsSource=\"{Binding FutureBirthdays}\"\u003e\n            \u003cListView.ItemTemplate\u003e\n                \u003cDataTemplate\u003e\n                    \u003cTextCell Text=\"{Binding Name}\" Detail=\"{Binding Birthday}\" /\u003e\n                \u003c/DataTemplate\u003e\n            \u003c/ListView.ItemTemplate\u003e\n        \u003c/ListView\u003e\n    \u003c/ContentView.Content\u003e\n\u003c/ContentView\u003e\n```\n\n### Key takeaway\n\n**MVVM** and **data binding** are the two most important aspects of any Xamarin Forms app. They enable decoupling, easy state managment and makes the app testable! If you remember only two things, remember these patterns.\n\n## Using a service to populate the app\n\nAdd the `NuGet-package` `Newtonsoft.Json` to the `Birthdays` project.\n\nCreate a folder called `Services` and add the class `BirthdayService`:\n\n```csharp\nusing System;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Threading.Tasks;\nusing Birthdays.Models;\nusing Newtonsoft.Json;\n\nnamespace Birthdays.Services {\n    public class BirthdayService {\n        public async Task\u003cPerson[]\u003e GetBirthdays() {\n            using (var httpClient = new HttpClient {\n                BaseAddress = new Uri(\"http://178.62.18.75:8081\")\n            }) {\n                var json = await httpClient.GetStringAsync(\"api/birthday/bodø\");\n                var birthdays = JsonConvert.DeserializeObject\u003cBirthdays\u003e(json);\n                var persons = birthdays.TodaysBirthdays.Concat(birthdays.NextBirthdays).ToArray();\n                return persons;\n            }\n        }\n\n        class Birthdays {\n            public Person[] TodaysBirthdays { get; set; }\n            public Person[] NextBirthdays { get; set; }\n        }\n    }\n}\n```\n\nSince the service doesn't use HTTPS, iOS at least will block the connection. We need to enable unsecure communication to this service. Open `Info.plist` from the `Birthdays.iOS` project and add the following:\n\n```XML\n\u003cdict\u003e\n    \u003ckey\u003eNSExceptionDomains\u003c/key\u003e\n    \u003cdict\u003e\n        \u003ckey\u003e178.62.18.75\u003c/key\u003e\n        \u003cdict\u003e\n            \u003ckey\u003eNSIncludesSubdomains\u003c/key\u003e\n            \u003ctrue/\u003e\n            \u003ckey\u003eNSExceptionAllowsInsecureHTTPLoads\u003c/key\u003e\n            \u003ctrue/\u003e\n            \u003ckey\u003eNSExceptionMinimumTLSVersion\u003c/key\u003e\n            \u003cstring\u003eTLSv1.2\u003c/string\u003e\n        \u003c/dict\u003e\n    \u003c/dict\u003e\n\u003c/dict\u003e\n```\n\nUse the service in `BirthdaysViewModel`:\n\n```csharp\nusing System;\nusing System.Linq;\nusing System.Collections.ObjectModel;\nusing System.Threading.Tasks;\nusing Birthdays.Models;\nusing Birthdays.Services;\n\nnamespace Birthdays.ViewModels {\n    public class BirthdaysViewModel {\n        readonly BirthdayService birthdayService;\n\n        public BirthdaysViewModel() {\n            birthdayService = new BirthdayService();\n        }\n\n        public Person ClosestBirthDay { get; private set; }\n        public ObservableCollection\u003cPerson\u003e FutureBirthdays { get; private set; }\n\n\n        public async Task FetchBirthdays() {\n            try {\n                var birthdays = await birthdayService.GetBirthdays();\n                ClosestBirthDay = birthdays[0];\n                FutureBirthdays = new ObservableCollection\u003cPerson\u003e(birthdays.Skip(1));\n            } catch (Exception) {\n                // TODO: Do some error handling\n            }\n        }\n    }\n}\n```\n\nAnd to fetch birthdays every time `BirthdaysPage` comes into view, change `BirthdaysPage.xaml.cs` to:\n\n```csharp\nusing Birthdays.ViewModels;\nusing Xamarin.Forms;\n\nnamespace Birthdays.Views {\n    public partial class BirthdaysPage : ContentPage {\n        readonly BirthdaysViewModel birthdaysViewModel;\n\n        public BirthdaysPage() {\n            InitializeComponent();\n            BindingContext = birthdaysViewModel = new BirthdaysViewModel();\n        }\n\n        protected async override void OnAppearing() {\n            base.OnAppearing();\n            await birthdaysViewModel.FetchBirthdays();\n        }\n    }\n}\n```\n\nAnd no data should be shown on screen. One piece is cleary missing from the data binding puzzle.\n\n### Key takeaway\n\n`JSON` and `HTTP` is the universal language of online services. `Postman` can be used to explore APIs. `HttpClient` the C# way of making HTTP-request, `Newtonsoft.Json` is the most used JSON-parser. `async` and `await` is the best thing since slices bread. `async void` is an anti-pattern and usage should be kept to a minimum. And sometimes you need to tweak your model to incorporate the real world.\n\n## Proper data binding\n\nImplement `INotifyPropertyChanged` in the `BirthdaysViewModel`: \n\n```csharp\nusing System;\nusing System.Linq;\nusing System.Collections.ObjectModel;\nusing System.Threading.Tasks;\nusing Birthdays.Models;\nusing Birthdays.Services;\nusing System.ComponentModel;\nusing System.Runtime.CompilerServices;\n\nnamespace Birthdays.ViewModels {\n    public class BirthdaysViewModel : INotifyPropertyChanged {\n        readonly BirthdayService birthdayService;\n\n        public BirthdaysViewModel() {\n            birthdayService = new BirthdayService();\n        }\n\n        public Person ClosestBirthDay { get; private set; }\n        public ObservableCollection\u003cPerson\u003e FutureBirthdays { get; private set; }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public async Task FetchBirthdays() {\n            try {\n                var birthdays = await birthdayService.GetBirthdays();\n                ClosestBirthDay = birthdays[0];\n                FutureBirthdays = new ObservableCollection\u003cPerson\u003e(birthdays.Skip(1));\n                OnPropertyChanged(nameof(ClosestBirthDay));\n                OnPropertyChanged(nameof(FutureBirthdays));\n            } catch (Exception) {\n                // TODO: Do some error handling\n            }\n        }\n\n        void OnPropertyChanged([CallerMemberName] string propertyName = null) =\u003e\n            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n    }\n}\n```\n\nRun the app and everything should work!\n\n### Key takeaway\n\n`INotifyPropertyChanged` is your new favourite interface. It binds the MVVM world together and is your bridge from ViewModel to View. Understand how this interface contains the event which makes the UI ask the ViewModel for new information!\n\n## Create a screen to add birthdays\n\nCreate `ValueConverters` folder in the `Views` folder, and add a class `NegateBooleanConverter`: \n\n```csharp\nusing System;\nusing System.Globalization;\nusing Xamarin.Forms;\n\nnamespace Birthdays.Views.ValueConverters {\n    public class NegateBooleanConverter : IValueConverter {\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n            =\u003e !(bool)value;\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n            =\u003e !(bool)value;\n    }\n}\n```\n\nChange `SettingsPage` to:\n\n```xaml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cContentPage xmlns=\"http://xamarin.com/schemas/2014/forms\" xmlns:x=\"http://schemas.microsoft.com/winfx/2009/xaml\" xmlns:valueConverters=\"clr-namespace:Birthdays.Views.ValueConverters\" x:Class=\"Birthdays.Views.SettingsPage\" Title=\"Settings\"\u003e\n    \u003cContentPage.Resources\u003e\n        \u003cResourceDictionary\u003e\n            \u003cvalueConverters:NegateBooleanConverter x:Key=\"negateBooleanConverter\" /\u003e\n        \u003c/ResourceDictionary\u003e\n    \u003c/ContentPage.Resources\u003e\n    \u003cContentPage.Content\u003e\n        \u003cStackLayout VerticalOptions=\"Center\"\u003e\n            \u003cLabel Text=\"Add new birthday\" FontSize=\"32\" FontAttributes=\"Bold\" Margin=\"15,30,15,15\" /\u003e\n            \u003cGrid Margin=\"15,0,15,0\"\u003e\n                \u003cGrid.ColumnDefinitions\u003e\n                    \u003cColumnDefinition Width=\"Auto\" /\u003e\n                    \u003cColumnDefinition Width=\"*\" /\u003e\n                \u003c/Grid.ColumnDefinitions\u003e\n                \u003cGrid.RowDefinitions\u003e\n                    \u003cRowDefinition Height=\"Auto\" /\u003e\n                    \u003cRowDefinition Height=\"Auto\" /\u003e\n                \u003c/Grid.RowDefinitions\u003e\n                \u003cLabel Text=\"Name\" VerticalOptions=\"Center\" HorizontalOptions=\"End\" /\u003e\n                \u003cEntry Grid.Column=\"1\" Placeholder=\"Name\" Text=\"{Binding Name}\" VerticalOptions=\"Center\" /\u003e\n                \u003cLabel Grid.Row=\"1\" Text=\"Birthday\" VerticalOptions=\"Center\" HorizontalOptions=\"End\" /\u003e\n                \u003cDatePicker Grid.Row=\"1\" Grid.Column=\"1\" MinimumDate=\"01/01/1900\" MaximumDate=\"{Binding Today}\" Date=\"{Binding Birthday}\" VerticalOptions=\"Center\" /\u003e\n            \u003c/Grid\u003e\n            \u003cActivityIndicator HeightRequest=\"50\" IsRunning=\"true\" IsVisible=\"{Binding ShowButton, Converter={StaticResource negateBooleanConverter}}}\" /\u003e\n            \u003cButton HeightRequest=\"50\" Text=\"Save\" Command=\"{Binding SaveCommand}\" IsVisible=\"{Binding ShowButton}\" /\u003e\n        \u003c/StackLayout\u003e\n    \u003c/ContentPage.Content\u003e\n\u003c/ContentPage\u003e\n```\n\n### Key takeaway\n\nDifferent layout options can be combined to get the desired view. Use a `ValueConverter` to change how a value is interpreted in XAML.\n\n## Bind the settings view\n\nCreate a new ViewModel, `AdminViewModel`:\n\n```csharp\nusing System;\nusing System.ComponentModel;\nusing System.Runtime.CompilerServices;\nusing System.Threading.Tasks;\nusing Xamarin.Forms;\n\nnamespace Birthdays.ViewModels {\n    public class AdminViewModel : INotifyPropertyChanged {\n        string name;\n        DateTime birthday;\n        bool showButton;\n\n        public AdminViewModel() {\n            SaveCommand = new Command(async () =\u003e await Save(), () =\u003e !string.IsNullOrEmpty(Name));\n            Today = Birthday = DateTime.Today;\n            ShowButton = true;\n        }\n\n        public DateTime Today { get; }\n\n        public string Name {\n            get { return name; }\n            set {\n                name = value;\n                OnPropertyChanged();\n                SaveCommand.ChangeCanExecute();\n            }\n        }\n\n        public DateTime Birthday {\n            get { return birthday; }\n            set {\n                birthday = value;\n                OnPropertyChanged();\n                SaveCommand.ChangeCanExecute();\n            }\n        }\n\n        public bool ShowButton {\n            get { return showButton; }\n            set { showButton = value; OnPropertyChanged(); }\n        }\n\n        public Command SaveCommand { get; }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        async Task Save() {\n            try {\n                ShowButton = false;\n                // TODO: Use a real service\n                await Task.Delay(3000);\n                ShowButton = true;\n            } catch (Exception) {\n                // TODO: Error handling\n            }\n        }\n\n        void OnPropertyChanged([CallerMemberName] string propertyName = null) =\u003e\n            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n    }\n}\n```\n\nAnd bind the view, creating the view model in `SettingsPage.xaml.cs`:\n\n```csharp\nusing Birthdays.ViewModels;\nusing Xamarin.Forms;\n\nnamespace Birthdays.Views {\n    public partial class SettingsPage : ContentPage {\n        public SettingsPage() {\n            InitializeComponent();\n            BindingContext = new AdminViewModel();\n        }\n    }\n}\n```\n\n### Key takeaway\n\nUse commands to bind actions to the view. All commands using external sources must be async. Give visual feedback both during progress and on completion. Disable or hide controls that should not be used at a given time.\n\n## Use a real service\n\nCheck Swagger for the required format and try it using Postman before implementing the API.\n\nThe update the `BirthdayService`, it should look like this now:\n\n```csharp\nusing System;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Birthdays.Models;\nusing Newtonsoft.Json;\n\nnamespace Birthdays.Services {\n    public class BirthdayService {\n        const string BaseAddress = \"http://178.62.18.75:8081\";\n\n        public async Task\u003cPerson[]\u003e GetBirthdays() {\n            using (var httpClient = new HttpClient {\n                BaseAddress = new Uri(BaseAddress)\n            }) {\n                var json = await httpClient.GetStringAsync(\"api/birthday/bodø/10\");\n                var birthdays = JsonConvert.DeserializeObject\u003cBirthdays\u003e(json);\n                var persons = birthdays.TodaysBirthdays.Concat(birthdays.NextBirthdays).ToArray();\n                return persons;\n            }\n        }\n\n        public async Task SaveBirthday(Person person) {\n            using (var httpClient = new HttpClient {\n                BaseAddress = new Uri(BaseAddress)\n            }) {\n                var personWithLocation = new PersonWithLocation(person);\n                var json = JsonConvert.SerializeObject(personWithLocation);\n                var content = new StringContent(json, Encoding.UTF8, \"application/json\");\n                var result = await httpClient.PostAsync(\"api/birthday\", content);\n                result.EnsureSuccessStatusCode();\n            }\n        }\n\n        class Birthdays {\n            public Person[] TodaysBirthdays { get; set; }\n            public Person[] NextBirthdays { get; set; }\n        }\n\n        class PersonWithLocation {\n            public PersonWithLocation(Person person) {\n                Name = person.Name;\n                Date = DateTime.Parse(person.Birthday);\n            }\n\n            public string Name { get; }\n            public DateTime Date { get; }\n            public string Location { get; } = \"bodø\";\n        }\n    }\n}\n```\n\nThen use the service in the view model:\n\n```csharp\nusing System;\nusing System.ComponentModel;\nusing System.Runtime.CompilerServices;\nusing System.Threading.Tasks;\nusing Birthdays.Models;\nusing Birthdays.Services;\nusing Xamarin.Forms;\n\nnamespace Birthdays.ViewModels {\n    public class AdminViewModel : INotifyPropertyChanged {\n        readonly BirthdayService birthdayService;\n\n        string name;\n        DateTime birthday;\n        bool showButton;\n\n        public AdminViewModel() {\n            birthdayService = new BirthdayService();\n            SaveCommand = new Command(async () =\u003e await Save(), () =\u003e !string.IsNullOrEmpty(Name));\n            Today = Birthday = DateTime.Today;\n            ShowButton = true;\n        }\n\n        public DateTime Today { get; }\n\n        public string Name {\n            get { return name; }\n            set {\n                name = value;\n                OnPropertyChanged();\n                SaveCommand.ChangeCanExecute();\n            }\n        }\n\n        public DateTime Birthday {\n            get { return birthday; }\n            set {\n                birthday = value;\n                OnPropertyChanged();\n                SaveCommand.ChangeCanExecute();\n            }\n        }\n\n        public bool ShowButton {\n            get { return showButton; }\n            set { showButton = value; OnPropertyChanged(); }\n        }\n\n        public Command SaveCommand { get; }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        async Task Save() {\n            try {\n                ShowButton = false;\n                var person = new Person(Name, Birthday);\n                await birthdayService.SaveBirthday(person);\n                Name = \"\";\n                Birthday = DateTime.Today;\n            } catch (Exception e) {\n                // TODO: Error handling\n            } finally {\n                ShowButton = true;\n            }\n        }\n\n        void OnPropertyChanged([CallerMemberName] string propertyName = null) =\u003e\n            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n    }\n}\n```\n\nYou can now create as many birthdays as you wish.\n\n### Key takeaway\n\nYour app's model should be perfect for your need, but again, sometimes you need to tweak the service layer to fit a reality that you have no control over.\n\n## Support deleting birthdays\n\nExpand `Person` to contain the `Id` property needed to support deletion:\n\n```csharp\nusing System;\n\nnamespace Birthdays.Models {\n    public class Person {\n        public Person(string name, DateTime date, int id) {\n            Name = name;\n            Birthday = date.ToShortDateString();\n            Age = GetAge(date);\n            Id = id;\n        }\n\n        public string Name { get; }\n        public string Birthday { get; }\n        public uint Age { get; }\n        public int Id { get; }\n\n        static uint GetAge(DateTime birthDate) {\n            var now = DateTime.Now;\n            int age = now.Year - birthDate.Year;\n            if (now.Month \u003c birthDate.Month || (now.Month == birthDate.Month \u0026\u0026 now.Day \u003c birthDate.Day)) {\n                age--;\n            }\n\n            return (uint)age;\n        }\n    }\n}\n```\n\nThis will break the app as the save functionality knows nothing of Ids. Create a new model class to better support saving and call it the confusing name `Birthday`:\n\n```csharp\nusing System;\n\nnamespace Birthdays.Models {\n    public class Birthday {\n        public Birthday(string name, DateTime birthdate) {\n            Name = name;\n            Birthdate = birthdate;\n        }\n\n        public string Name { get; }\n        public DateTime Birthdate { get; }\n    }\n}\n```\n\nUpdate `AdminViewModel` to use `Birthday` instead of Person in the `Save` method.\n\nUtilize `Birthday` in the `BirthdayService` and add support for the deletion operation:\n\n```csharp\nusing System;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Birthdays.Models;\nusing Newtonsoft.Json;\n\nnamespace Birthdays.Services {\n    public class BirthdayService {\n        const string BaseAddress = \"http://178.62.18.75:8081\";\n        const string BirthdayAPI = \"api/birthday\";\n        const string DefaultLocation = \"bodø\";\n\n        public async Task\u003cPerson[]\u003e GetBirthdays() {\n            using (var httpClient = HttpClientFactory.CreateClient()) {\n                const int MaxResults = 10;\n                var json = await httpClient.GetStringAsync($\"{BirthdayAPI}/{DefaultLocation}/{MaxResults}\");\n                var birthdays = JsonConvert.DeserializeObject\u003cBirthdays\u003e(json);\n                var persons = birthdays.TodaysBirthdays.Concat(birthdays.NextBirthdays).ToArray();\n                return persons;\n            }\n        }\n\n        public async Task SaveBirthday(Birthday birthday) {\n            using (var httpClient = HttpClientFactory.CreateClient()) {\n                var personWithLocation = new BirthdayWithLocation(birthday);\n                var json = JsonConvert.SerializeObject(personWithLocation);\n                var content = new StringContent(json, Encoding.UTF8, \"application/json\");\n                var result = await httpClient.PostAsync(BirthdayAPI, content);\n                result.EnsureSuccessStatusCode();\n            }\n        }\n\n        public async Task DeleteBirthday(Person person) {\n            using (var httpClient = HttpClientFactory.CreateClient()) {\n                await httpClient.DeleteAsync($\"{BirthdayAPI}/{person.Id}\");\n            }\n        }\n\n        class Birthdays {\n            public Person[] TodaysBirthdays { get; set; }\n            public Person[] NextBirthdays { get; set; }\n        }\n\n        class BirthdayWithLocation {\n            readonly Birthday birthday;\n\n            public BirthdayWithLocation(Birthday birthday) {\n                this.birthday = birthday;\n            }\n\n            public string Name { get { return birthday.Name; } }\n            public string Date { get { return birthday.Birthdate.ToShortDateString(); } }\n            public string Location { get { return DefaultLocation; } }\n        }\n\n        static class HttpClientFactory {\n            public static HttpClient CreateClient()\n                =\u003e new HttpClient {\n                    BaseAddress = new Uri(BaseAddress)\n                };\n        }\n    }\n}\n```\n\nTo add support for deletion in `BirthdaysViewModel`, add the followwing method:\n\n```csharp\npublic async Task RemoveBirthday(Person person) {\n    try {\n        FutureBirthdays.Remove(person);\n        await birthdayService.DeleteBirthday(person);\n    } catch (Exception) {\n        // TODO: Do some error handling\n    }\n}\n```\n\nAdd support for delete context action in `BirthdaysView`, aka swipe to delete on iOS:\n\n```xaml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cContentView xmlns=\"http://xamarin.com/schemas/2014/forms\" xmlns:x=\"http://schemas.microsoft.com/winfx/2009/xaml\" x:Class=\"Birthdays.Views.BirthdaysView\"\u003e\n    \u003cContentView.Content\u003e\n        \u003cListView ItemsSource=\"{Binding FutureBirthdays}\"\u003e\n            \u003cListView.ItemTemplate\u003e\n                \u003cDataTemplate\u003e\n                    \u003cTextCell Text=\"{Binding Name}\" Detail=\"{Binding Birthday}\"\u003e\n                        \u003cTextCell.ContextActions\u003e\n                            \u003cMenuItem Clicked=\"OnDelete\" CommandParameter=\"{Binding .}\" Text=\"Delete\" IsDestructive=\"True\" /\u003e\n                        \u003c/TextCell.ContextActions\u003e\n                    \u003c/TextCell\u003e\n                \u003c/DataTemplate\u003e\n            \u003c/ListView.ItemTemplate\u003e\n        \u003c/ListView\u003e\n    \u003c/ContentView.Content\u003e\n\u003c/ContentView\u003e\n```\n\nFinally, since this functionality in Xamarin Forms has no binding support out of the box, we need to manually notify the view model of the wanted delition. `BirthdaysView.xaml.cs` thus looks like this:\n\n```csharp\nusing System;\nusing Birthdays.Models;\nusing Birthdays.ViewModels;\nusing Xamarin.Forms;\n\nnamespace Birthdays.Views {\n    public partial class BirthdaysView : ContentView {\n        public BirthdaysView() {\n            InitializeComponent();\n        }\n\n        public async void OnDelete(object sender, EventArgs e) {\n            var menuItem = (MenuItem)sender;\n            var person = (Person)menuItem.CommandParameter;\n            var birthdaysViewModel = (BirthdaysViewModel)BindingContext;\n            await birthdaysViewModel.RemoveBirthday(person);\n        }\n    }\n}\n```\n\nPhew.\n\n### Key takeaway\n\nA design is good if existing code needs small to no changes in order to add new functionality. If you repeat yourself too much, or the code doesn't express its intent clearly, take a breather to refactor. Refactoring prevents code-rot.\n\n## Adding unit tests\n\nCreate a new `xUnit Test Project` named `Tests`.\n\nUpdate the nuget packages to the latest versions.\n\nAdd a reference to the .Net Standard Library of the app, the `Birthdays` project.\n\nPerson contains a cryptic `GetAge` method, so this class is a good first case. Rename `UnitTest1` to `PersonTests`, and add a test:\n\n```csharp\nusing System;\nusing Birthdays.Models;\nusing Xunit;\n\nnamespace Tests {\n    public class PersonTests {\n        [Fact]\n        public void VerifyPerson() {\n            var birthdate = new DateTime(1983, 9, 8);\n\n            var runar = new Person(\"Runar\", birthdate, 0);\n\n            Assert.Equal(\"Runar\", runar.Name);\n            Assert.Equal(birthdate, DateTime.Parse(runar.Birthday));\n            Assert.Equal((uint)35, runar.Age);\n            Assert.Equal(0, runar.Id);\n        }\n    }\n}\n```\n\nThis should run green and increase our confidence that our code is correct.\n\nBut the test has one obvious flaw. What happens on 8.9.2019? We've create the yearly failed test. Time needs to be considered an external dependency and treated as such.\n\nWe need to create our own `DateTime` static class to control time. From the test, we must change what *Now* means.\n\nAdd the following classes, `DateTimeProvider` and `DateTimeProviderContext`, to a `Helpers` folder in the `Birthdays` project:\n\n```csharp\nusing System;\nnamespace Birthdays.Helpers {\n    public class DateTimeProvider {\n        static Lazy\u003cDateTimeProvider\u003e instance = new Lazy\u003cDateTimeProvider\u003e(() =\u003e new DateTimeProvider());\n\n        DateTimeProvider() {\n        }\n\n        public static DateTimeProvider Instance {\n            get { return instance.Value; }\n        }\n\n        Func\u003cDateTime\u003e _defaultCurrentFunction = () =\u003e DateTime.UtcNow;\n\n        public DateTime Now {\n            get {\n                return DateTimeProviderContext.Current == null\n                     ? _defaultCurrentFunction.Invoke()\n                     : DateTimeProviderContext.Current.ContextDateTimeNow;\n            }\n        }\n    }\n}\n```\n\n```csharp\nusing System;\nusing System.Collections;\nusing System.Threading;\n\nnamespace Birthdays.Helpers {\n    public class DateTimeProviderContext : IDisposable {\n        readonly Stack contextStack = new Stack();\n\n        static ThreadLocal\u003cStack\u003e ThreadScopeStack = new ThreadLocal\u003cStack\u003e(() =\u003e new Stack());\n\n        public DateTime ContextDateTimeNow;\n\n        public DateTimeProviderContext(DateTime contextDateTimeNow) {\n            ContextDateTimeNow = contextDateTimeNow;\n            ThreadScopeStack.Value.Push(this);\n        }\n\n        public static DateTimeProviderContext Current {\n            get {\n                if (ThreadScopeStack.Value.Count == 0) {\n                    return null;\n                }\n\n                return (DateTimeProviderContext)ThreadScopeStack.Value.Peek();\n            }\n        }\n\n        public void Dispose() =\u003e ThreadScopeStack.Value.Pop();\n    }\n}\n```\n\nUse it in `Person`:\n\n```csharp\n...\nvar now = DateTimeProvider.Instance.Now;\n...\n```\n\nAnd in the tests:\n\n```csharp\nusing System;\nusing Birthdays.Helpers;\nusing Birthdays.Models;\nusing Xunit;\n\nnamespace Tests {\n    public class PersonTests {\n        [Fact]\n        public void VerifyPerson() {\n            var currentDate = new DateTime(2019, 2, 6);\n            using (var context1 = new DateTimeProviderContext(currentDate)) {\n                var birthdate = new DateTime(1983, 9, 8);\n\n                var runar = new Person(\"Runar\", birthdate, 0);\n\n                Assert.Equal(\"Runar\", runar.Name);\n                Assert.Equal(birthdate, DateTime.Parse(runar.Birthday));\n                Assert.Equal((uint)35, runar.Age);\n                Assert.Equal(0, runar.Id);\n            }\n        }\n    }\n}\n```\n\n### Key takeaway\n\nWrite unit test for the parts of your app that you need to work. Control for all external factors to make your tests solid. If you write tests after the production code, always verify that the tests will fail for the right reasons.\n\n## Testing a view model\n\nCreate `BirthdaysViewModelTests`:\n\n```csharp\nusing System.Threading.Tasks;\nusing Birthdays.ViewModels;\nusing Xunit;\n\nnamespace Tests {\n    public class BirthdaysViewModelTests {\n        [Fact]\n        public async Task FetchBirthdays() {\n            var birthdayViewModel = new BirthdaysViewModel();\n\n            await birthdayViewModel.FetchBirthdays();\n\n            Assert.NotNull(birthdayViewModel.ClosestBirthDay);\n            Assert.True(birthdayViewModel.FutureBirthdays.Count \u003e 0);\n        }\n    }\n}\n```\n\nVerify that the test works.\n\nThis is a good start, but it does not check the property changed notifications are fired. Let's fix that. Create a `Helper` folder in the test project and add the class `PropertyChangedTracker`.\n\n```csharp\nusing System;\nusing System.Linq;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing Xunit;\n\nnamespace Tests.Helpers {\n    public class PropertyChangeTracker : IDisposable {\n        readonly INotifyPropertyChanged changer;\n        readonly List\u003cstring\u003e notifications;\n\n        public PropertyChangeTracker(INotifyPropertyChanged changer) {\n            this.changer = changer;\n            notifications = new List\u003cstring\u003e();\n            changer.PropertyChanged += Changer_PropertyChanged;\n        }\n\n        void Changer_PropertyChanged(object sender, PropertyChangedEventArgs e)\n            =\u003e notifications.Add(e.PropertyName);\n\n        public void Dispose()\n            =\u003e changer.PropertyChanged -= Changer_PropertyChanged;\n\n        public void VerifyNumberOfNotifications(int expected)\n            =\u003e Assert.Equal(expected, notifications.Count);\n\n        public void VerifyNotificationOfName(string propertyName, int times = 1)\n            =\u003e Assert.Equal(times, notifications.Count(p =\u003e propertyName == p));\n    }\n}\n```\n\nUse it in the test:\n\n```csharp\nusing System.Threading.Tasks;\nusing Birthdays.ViewModels;\nusing Tests.Helpers;\nusing Xunit;\n\nnamespace Tests {\n    public class BirthdaysViewModelTests {\n        [Fact]\n        public async Task FetchBirthdays() {\n            var birthdayViewModel = new BirthdaysViewModel();\n            using (var propertyChangedTracker = new PropertyChangeTracker(birthdayViewModel)) {\n                await birthdayViewModel.FetchBirthdays();\n\n                Assert.NotNull(birthdayViewModel.ClosestBirthDay);\n                Assert.True(birthdayViewModel.FutureBirthdays.Count \u003e 0);\n                propertyChangedTracker.VerifyNumberOfNotifications(2);\n                propertyChangedTracker.VerifyNotificationOfName(\"ClosestBirthDay\");\n                propertyChangedTracker.VerifyNotificationOfName(\"FutureBirthdays\");\n            }\n        }\n    }\n}\n```\n\n### Key takeaway\n\nMVVM is a really testable pattern and unit tests can help you verify even your UI.\n\n## Dependency Injection\n\nThis naive test still has one fatal flaw: we use the production service in our tests! To remedy this, we can create a simple mock and avoiding the network call altogether.\n\nStart with creating an interface for the service, `IBirthdayService`:\n\n```csharp\nusing System.Threading.Tasks;\nusing Birthdays.Models;\n\nnamespace Birthdays.Services {\n    public interface IBirthdayService {\n        Task\u003cPerson[]\u003e GetBirthdays();\n        Task SaveBirthday(Birthday birthday);\n        Task DeleteBirthday(Person person);\n    }\n}\n```\n\n`BirthdayService` needs to implement it:\n\n```csharp\n...\npublic class BirthdayService : IBirthdayService {\n...\n```\n\nUse depency injection to inject `BirthdayService` using the constructor in `BirthdaysViewModel`: \n\n```csharp\n...\nreadonly IBirthdayService birthdayService;\n\npublic BirthdaysViewModel(IBirthdayService birthdayService) {\n    this.birthdayService = birthdayService;\n}\n...\n```\n\nUpdate `BirthdaysPage.xaml.cs` to create `BirthdayService`:\n\n```csharp\n...\nBindingContext = birthdaysViewModel = new BirthdaysViewModel(new BirthdayService());\n...\n```\n\nCreate the stub and do the same in the tests:\n\n```csharp\nusing System;\nusing System.Threading.Tasks;\nusing Birthdays.Models;\nusing Birthdays.Services;\nusing Birthdays.ViewModels;\nusing Tests.Helpers;\nusing Xunit;\n\nnamespace Tests {\n    public class BirthdaysViewModelTests {\n        [Fact]\n        public async Task FetchBirthdays() {\n            var birthdayViewModel = new BirthdaysViewModel(new BirthdayServiceFake());\n            using (var propertyChangedTracker = new PropertyChangeTracker(birthdayViewModel)) {\n                await birthdayViewModel.FetchBirthdays();\n\n                Assert.NotNull(birthdayViewModel.ClosestBirthDay);\n                Assert.True(birthdayViewModel.FutureBirthdays.Count \u003e 0);\n                propertyChangedTracker.VerifyNumberOfNotifications(2);\n                propertyChangedTracker.VerifyNotificationOfName(\"ClosestBirthDay\");\n                propertyChangedTracker.VerifyNotificationOfName(\"FutureBirthdays\");\n            }\n        }\n\n        class BirthdayServiceFake : IBirthdayService {\n            public Task DeleteBirthday(Person person) {\n                throw new System.NotImplementedException();\n            }\n\n            public Task\u003cPerson[]\u003e GetBirthdays() {\n                return Task.FromResult(new[] {\n                    new Person(\"Runar\", new DateTime(1983, 9, 8), 1),\n                    new Person(\"Anders\", new DateTime(1960, 12, 24), 2)\n                });\n            }\n\n            public Task SaveBirthday(Birthday birthday) {\n                throw new System.NotImplementedException();\n            }\n        }\n    }\n}\n```\n\n### Key takeaway\n\nUse dependency inject to control your dependencies. ViewModel contains logic for the view and should be tested thusly.\n\n## Adding an integrated test\n\nServices should be tested using an integrated test, using the real implementation. These tests will be slower and more vulnerable, but are invaluable to ensure correctness and to detect breaking changes. \n\nUpdate `BirthdayService` to take the location as a parameter. We'll use this to create a list just for the tests. *bodø* is still default.\n\n```csharp\nusing System;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Birthdays.Models;\nusing Newtonsoft.Json;\n\nnamespace Birthdays.Services {\n    public class BirthdayService : IBirthdayService {\n        const string BaseAddress = \"http://178.62.18.75:8081\";\n        const string BirthdayAPI = \"api/birthday\";\n\n        readonly string location;\n\n        public BirthdayService(string location = \"bodø\") {\n            this.location = location;\n        }\n\n        public async Task\u003cPerson[]\u003e GetBirthdays() {\n            using (var httpClient = HttpClientFactory.CreateClient()) {\n                const int MaxResults = 10;\n                var json = await httpClient.GetStringAsync($\"{BirthdayAPI}/{location}/{MaxResults}\");\n                var birthdays = JsonConvert.DeserializeObject\u003cBirthdays\u003e(json);\n                var persons = birthdays.TodaysBirthdays.Concat(birthdays.NextBirthdays).ToArray();\n                return persons;\n            }\n        }\n\n        public async Task SaveBirthday(Birthday birthday) {\n            using (var httpClient = HttpClientFactory.CreateClient()) {\n                var personWithLocation = new BirthdayWithLocation(birthday, location);\n                var json = JsonConvert.SerializeObject(personWithLocation);\n                var content = new StringContent(json, Encoding.UTF8, \"application/json\");\n                var result = await httpClient.PostAsync(BirthdayAPI, content);\n                result.EnsureSuccessStatusCode();\n            }\n        }\n\n        public async Task DeleteBirthday(Person person) {\n            using (var httpClient = HttpClientFactory.CreateClient()) {\n                await httpClient.DeleteAsync($\"{BirthdayAPI}/{person.Id}\");\n            }\n        }\n\n        class Birthdays {\n            public Person[] TodaysBirthdays { get; set; }\n            public Person[] NextBirthdays { get; set; }\n        }\n\n        class BirthdayWithLocation {\n            readonly Birthday birthday;\n            readonly string location;\n\n            public BirthdayWithLocation(Birthday birthday, string location) {\n                this.birthday = birthday;\n                this.location = location;\n            }\n\n            public string Name { get { return birthday.Name; } }\n            public string Date { get { return birthday.Birthdate.ToShortDateString(); } }\n            public string Location { get { return location; } }\n        }\n\n        static class HttpClientFactory {\n            public static HttpClient CreateClient()\n                =\u003e new HttpClient {\n                    BaseAddress = new Uri(BaseAddress)\n                };\n        }\n    }\n}\n```\n\nCreate `BirthdayServiceTests`:\n\n```csharp\nusing System;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Birthdays.Models;\nusing Birthdays.Services;\nusing Xunit;\n\nnamespace Tests {\n    public class BirthdayServiceTests {\n        [Fact]\n        public async Task VerifyBirthdayService() {\n            var uniqueLocation = Guid.NewGuid().ToString(\"N\");\n            var birthdayService = new BirthdayService(uniqueLocation);\n\n            var birthdate = DateTime.Today + TimeSpan.FromDays(1);\n            var uniqueName = Guid.NewGuid().ToString(\"N\");\n            var birthday = new Birthday(uniqueName, birthdate);\n\n            await birthdayService.SaveBirthday(birthday);\n\n            var persons = await birthdayService.GetBirthdays();\n\n            Assert.True(persons.Any());\n            Assert.Equal(birthday.Name, persons[0].Name);\n\n            await birthdayService.DeleteBirthday(persons[0]);\n\n            persons = await birthdayService.GetBirthdays();\n\n            Assert.False(persons.Any());\n        }\n    }\n}\n```\n\n### Key takeaway\n\nUse integrated tests where necessary. Do not repeat logic from unit tests. Remember to clean up after each test.\n\n## Using an iOS custom renderer to disable selection in lists\n\nSelection is enabled in Xamarin Forms ListViews per default. It's easy to disable this using a custom renderer. Create a new folder `Renderers` in the iOS project add a regular C# class `NoSelectListViewRenderer`. It should look like this:\n\n```csharp\nusing Birthdays.iOS.Renderers;\nusing Xamarin.Forms;\nusing Xamarin.Forms.Platform.iOS;\n\n[assembly: ExportRenderer(typeof(ListView), typeof(NoSelectListViewRenderer))]\nnamespace Birthdays.iOS.Renderers {\n    public class NoSelectListViewRenderer : ListViewRenderer {\n        protected override void OnElementChanged(ElementChangedEventArgs\u003cListView\u003e e) {\n            base.OnElementChanged(e);\n            Control.AllowsSelection = false;\n        }\n    }\n}\n```\n\n### Key takeaway\n\nUse custom renderers per platform to have easy access to native functionality.  When needed, don't overdo it.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhjerpbakk%2Fxamarin-forms-birthday","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhjerpbakk%2Fxamarin-forms-birthday","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhjerpbakk%2Fxamarin-forms-birthday/lists"}