{"id":16202195,"url":"https://github.com/francedot/cnhindustrial-xamarindemo","last_synced_at":"2025-04-07T18:53:24.362Z","repository":{"id":93070314,"uuid":"129367447","full_name":"francedot/CNHIndustrial-XamarinDemo","owner":"francedot","description":"CNH Industrial - Hands On Lab","archived":false,"fork":false,"pushed_at":"2018-04-13T09:11:17.000Z","size":247,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-13T20:37:33.417Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/francedot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-04-13T07:44:13.000Z","updated_at":"2022-09-15T20:19:34.000Z","dependencies_parsed_at":"2023-04-23T12:31:31.525Z","dependency_job_id":null,"html_url":"https://github.com/francedot/CNHIndustrial-XamarinDemo","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/francedot%2FCNHIndustrial-XamarinDemo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/francedot%2FCNHIndustrial-XamarinDemo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/francedot%2FCNHIndustrial-XamarinDemo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/francedot%2FCNHIndustrial-XamarinDemo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/francedot","download_url":"https://codeload.github.com/francedot/CNHIndustrial-XamarinDemo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247713295,"owners_count":20983682,"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":[],"created_at":"2024-10-10T09:46:11.927Z","updated_at":"2025-04-07T18:53:24.325Z","avatar_url":"https://github.com/francedot.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"## CNH Industrial - Hands On Lab\n\nToday we will build a cloud connected [Xamarin.Forms](http://xamarin.com/forms) application that will display a list of Movies. We will start by building the Models, View and ViewModels and then we will connect the app to an Azure Mobile App backend in just a few lines of code.\n\n### Get Started\n\nOpen **Start/Movies/Movies.sln**\n\nThis solution contains 4 projects\n\n* Movies - .NET Standard Project containing the shared code (model, views, and view models)\n* Movies.Droid - Xamarin.Android application\n* Movies.iOS - Xamarin.iOS application (requires a macOS build host)\n* Movies.UWP - Windows 10 UWP application (requires Visual Studio 2015/2017 on Windows 10)\n\n![Solution](https://i.imgur.com/FQxgRep.png)\n\nThe **Movies** project also has blank code files and XAML pages that we will use during the Hands on Lab.\n\n#### NuGet Restore\n\nAll projects have the required NuGet packages already installed, so there will be no need to install additional packages during the Hands on Lab. The first thing that we must do is restore all of the NuGet packages from the internet.\n\nThis can be done by **Right-clicking** on the **Solution** and selecting **Restore NuGet packages...**\n\n### Model\n\nWe will download details about the Movies. Open the **Movies/Model/Movie.cs** file and add the following properties to the **Movie** class:\n\n```csharp\npublic string Id { get; set; }\npublic string Title { get; set; }\npublic string Year { get; set; }\npublic string Director { get; set; }\npublic string Country { get; set; }\npublic string Poster { get; set; }\npublic double Rating { get; set; }\npublic string Genre { get; set; }\npublic string Plot { get; set; }\n```\n\n### View Model\n\nThe **PageViewModelBase.cs** will provide all of the functionality to display data in our our main Xamarin.Forms Page. It will consist of a list of movies and a method that can be called to get the movies from the server.\n\n#### Implementing INotifyPropertyChanged\n\n*INotifyPropertyChanged* is important for data binding in MVVM Frameworks. This is an interface that, when implemented, lets our view know about changes to the model.\n\nUpdate:\n\n```csharp\npublic abstract class ViewModelBase\n{\n}\n```\n\nto\n\n```csharp\npublic abstract class ViewModelBase : INotifyPropertyChanged\n{\n    #region INotifyPropertyChanged implementation\n\n    public event PropertyChangedEventHandler PropertyChanged;\n\n    public void OnPropertyChanged(string name = \"\")\n    {\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));\n    }\n\n    #endregion\n}\n```\nRight click and tap **Implement Interface**, which will add the following line of code:\n\n```csharp\npublic event PropertyChangedEventHandler PropertyChanged;\n```\n\nWe will code the helper methods **OnPropertyChanged** and **Set** that will raise the **PropertyChanged** event (see below). We will invoke the helper method whenever a property changes.\n\n```csharp\npublic void OnPropertyChanged(string name = \"\")\n{\n    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));\n}\n```\n\n```csharp\nprotected bool Set\u003cT\u003e(ref T field, T value, [CallerMemberName] string name = null)\n{\n    if (Equals(field, value))\n        return false;\n\n    field = value;\n    OnPropertyChanged(name);\n    return true;\n}\n```\n\n```csharp\npublic class PageViewModelBase : ViewModelBase\n{\n    // Shared fields\n}\n```\n\n```csharp\npublic class MainPageViewModel : PageViewModelBase\n{\n    // MainPage specific\n}\n```\n\nAs we will see next, whenever we want to raise a property binding update, we call the helper method Set.\n\nPageViewModelBase will be our base class for every PageViewModel. Here we can store fields, properties and method that will be used across every PageViewModel (e.g. IsLoading property and a service to sync movies with server)\n\n#### IsLoading\nIn **PageViewModelBase**, we will create a backing field and accessors for a boolean property. This will let our view know that our view model is busy so we don't perform duplicate operations (like allowing the user to refresh the data multiple times).\n\nFirst, create the backing field:\n\n```csharp\nprivate bool _isLoading;\n```\n\nNext, create the property:\n\n```csharp\npublic bool IsLoading\n{\n    get =\u003e _isLoading;\n    set =\u003e Set(ref _isLoading, value);\n}\n```\n\nNotice that we call **Set\u003cT\u003e();** when the value changes. The Xamarin.Forms binding infrastructure will subscribe to our **PropertyChanged** event so the UI will be notified of the change.\n\n#### ObservableCollection of Movies\n\nAgain in **PageViewModelBase**, we will use an **ObservableCollection\u003cMovie\u003e** that will be cleared and then loaded with **Movie** objects. We use an **ObservableCollection** because it has built-in support to raise **CollectionChanged** events when we Add or Remove items from the collection. This means we don't call **OnPropertyChanged** when updating the collection.\nThe _allMovies field will be shared across the same instance of PageViewModels and hence it can be made static.\n\n```csharp\nprivate static ObservableCollection\u003cMovie\u003e _allMovies;\n\npublic ObservableCollection\u003cMovie\u003e AllMovies\n{\n    get =\u003e _allMovies;\n    set =\u003e Set(ref _allMovies, value);\n}\n```\n\nFrom **View \u003e Task List**, You can now uncomment items labeled with **TODO Uncomment**.\n\n#### Refresh Movies Command\n\nA command is an action that can be data bounded to a View property, e.g. the Command property of a Button.\nWe will add this property in DetailPageViewModel in order to trigger the Push and Pull (Sync) of items from our app mobile backend. Let's do so!\n\nFirstly, in **DetailPageViewModel.cs** add a property named **RefreshCommand**\n\n```csharp\npublic Command RefreshCommand { get; }\n```\n\nAfter that, at the top of the constructor add the following initialization logic\n\n```csharp\nRefreshCommand = new Command(() =\u003e\n{\n    MessagingCenter.Send\u003cDetailPageViewModel\u003e(this, \"RefreshMovies\");\n});\n```\n\nThe command action will leverage **MessagingCenter** APIs to notify the parent PageViewModel, namely MainPageViewModel, to sync backend Movies.\nAs a matter of fact, update the recipient code in MainPageViewModel constructor as follows:\n\n```csharp\nMessagingCenter.Subscribe\u003cDetailPageViewModel\u003e(this,\n                \"RefreshMovies\", async sender =\u003e await RefreshAsync());\n```\n\nLastly, provide the refresh logic for Movies along with available genres as follows\n\n```csharp\nIsLoading = true;\nDetailPageViewModel.RefreshCommand.ChangeCanExecute();\n\nAllMovies = new ObservableCollection\u003cMovie\u003e(await MoviesSource.GetMoviesAsync());\nMenuPageViewModel.UpdateGenres();\n\nIsLoading = false;\nDetailPageViewModel.RefreshCommand.ChangeCanExecute();\n```\n\nLet's now add the missing piece of UI in **DetailPage.xaml**\n\n## DetailPage - ToolbarItems and ListView\n\nWhat we want here is to have a list of Movies to be displayed to the user and also a Toolbar button that will trigger the Movies refresh.\n\nFollowing XAML Property Syntax, add the following piece of markup to create a ToolBarItem to be displayed in the top app bar.\n\n```xml\n\u003cContentPage.ToolbarItems\u003e\n    \u003cToolbarItem Text=\"Refresh\" Command=\"{Binding RefreshCommand}\"\u003e\n        \u003cToolbarItem.Icon\u003e\n            \u003cOnPlatform x:TypeArguments=\"FileImageSource\"\u003e\n                \u003cOn Platform=\"Android\" Value=\"Assets/Refresh.png\"/\u003e\n                \u003cOn Platform=\"Windows\" Value=\"Assets/Refresh.png\"/\u003e\n                \u003cOn Platform=\"iOS\" Value=\"\"/\u003e\n            \u003c/OnPlatform\u003e\n        \u003c/ToolbarItem.Icon\u003e\n    \u003c/ToolbarItem\u003e\n\u003c/ContentPage.ToolbarItems\u003e\n```\n\nThen, let's create a ListView and a Progress Ring to be data bounded to the backing ViewModel properties.\n\n```xml\n\u003cGrid\u003e\n    \u003cListView CachingStrategy=\"RecycleElement\"\n            ItemsSource=\"{Binding Movies}\"\n            RowHeight=\"220\"\n            ItemTapped=\"ListView_OnItemTapped\"\n            IsPullToRefreshEnabled=\"True\"\n            Refreshing=\"ListView_OnRefreshing\"\n            RefreshCommand=\"{Binding RefreshCommand}\"\u003e\n        \u003cListView.ItemTemplate\u003e\n            \u003cDataTemplate\u003e\n                \u003cViewCell\u003e\n                    \u003cViewCell.ContextActions\u003e\n                        \u003cMenuItem Clicked=\"OnDeleteItem\"\n                                CommandParameter=\"{Binding .}\"\n                                Text=\"Delete\"\n                                IsDestructive=\"True\" /\u003e\n                    \u003c/ViewCell.ContextActions\u003e\n                    \u003cGrid\u003e\n                        \u003cGrid.ColumnDefinitions\u003e\n                            \u003cColumnDefinition Width=\"148\"/\u003e\n                            \u003cColumnDefinition Width=\"*\"/\u003e\n                        \u003c/Grid.ColumnDefinitions\u003e\n                        \u003cImage Source=\"{Binding Poster}\"\n                            HeightRequest=\"220\"\n                            Aspect=\"Fill\"/\u003e\n                        \u003cGrid Grid.Column=\"1\" Padding=\"4\"\u003e\n                            \u003cGrid.RowDefinitions\u003e\n                                \u003cRowDefinition Height=\"*\"/\u003e\n                                \u003cRowDefinition Height=\"*\"/\u003e\n                            \u003c/Grid.RowDefinitions\u003e\n                            \u003cStackLayout\u003e\n                                \u003cLabel Text=\"{Binding Title}\"\n                                    FontSize=\"20\"\n                                    FontAttributes=\"Bold\"/\u003e\n                                \u003cLabel Text=\"{Binding Director}\"\n                                    FontSize=\"16\"\n                                    FontAttributes=\"Italic\"/\u003e\n                            \u003c/StackLayout\u003e\n                            \u003cGrid Grid.Row=\"1\"\n                                VerticalOptions=\"End\"\n                                Margin=\"0,0,0,8\"\u003e\n                                \u003cGrid.ColumnDefinitions\u003e\n                                    \u003cColumnDefinition Width=\"4*\"/\u003e\n                                    \u003cColumnDefinition Width=\"*\"/\u003e\n                                \u003c/Grid.ColumnDefinitions\u003e\n                                \u003cStackLayout Orientation=\"Horizontal\"\u003e\n                                    \u003cLabel Text=\"{Binding Country}\" /\u003e\n                                    \u003cLabel Text=\"-\" /\u003e\n                                    \u003cLabel Text=\"{Binding Year}\" /\u003e\n                                \u003c/StackLayout\u003e\n                                \u003cLabel Grid.Column=\"1\"\n                                    FontSize=\"16\"\n                                    FontAttributes=\"Bold\"\n                                    HorizontalOptions=\"End\"\n                                    Text=\"{Binding Rating, StringFormat='{0:0.0}'}\"\n                                    Margin=\"0,0,4,0\"/\u003e\n                            \u003c/Grid\u003e\n                        \u003c/Grid\u003e\n                    \u003c/Grid\u003e\n                \u003c/ViewCell\u003e\n            \u003c/DataTemplate\u003e\n        \u003c/ListView.ItemTemplate\u003e\n    \u003c/ListView\u003e\n    \u003cActivityIndicator VerticalOptions=\"Center\"\n                        HorizontalOptions=\"Center\"\n                        IsRunning=\"{Binding IsLoading}\"\n                        IsVisible=\"{Binding IsLoading}\"/\u003e\n\u003c/Grid\u003e\n```\n\nNow the DetailPage UI is ready to display data that will come from our source, such as data coming from an Azure Mobile App.\n\n## Connect to Azure Mobile Apps\n\nBeing able to grab data from a RESTful end point is great, but what about creating the back-end service? This is where Azure Mobile Apps comes in. Let's update our application to use an Azure Mobile Apps back-end.\n\nIf you don't already have an Azure account, go to [http://portal.azure.com](http://portal.azure.com) and register.\n\nOnce you're registered, open the Azure portal, select the **+ New** button and search for **mobile apps**. You will see the results as shown below. Select **Mobile Apps Quickstart**\n\n![Quickstart](http://content.screencast.com/users/JamesMontemagno/folders/Jing/media/c2894f06-c688-43ad-b812-6384b34c5cb0/2016-07-11_1546.png)\n\nThe Quickstart blade will open, select **Create**\n\n![Create quickstart](http://content.screencast.com/users/JamesMontemagno/folders/Jing/media/344d6fc2-1771-4cb7-a49a-6bd9e9579ba6/2016-07-11_1548.png)\n\nThis will open a settings blade with 4 settings:\n\n**App name**\n\nThis is a unique name for the app that you will need when connecting your Xamarin.Forms client app to the hosted Azure Mobile App. You will need to choose a globally-unique name; for example, you could try something like *yourlastnameMovies*.\n\n**Subscription**\nSelect a subscription or create a pay-as-you-go account (this service will not cost you anything).\n\n**Resource Group**\nSelect *Create new* and call it **Movies**.\n\nA resource group is logical container the can hold multiple Azure services. Using a resource group allows you to delete a collection of related services in one step.\n\n**App Service plan/Location**\nClick this field and select **Create New**, give it a unique name, select a location (typically you would choose a location close to your customers), and then select the F1 Free tier:\n\n![service plan](http://content.screencast.com/users/JamesMontemagno/folders/Jing/media/7559d3f1-7ee6-490f-ac5e-d1028feba88f/2016-07-11_1553.png)\n\nFinally check **Pin to dashboard** and click create:\n\n![](http://content.screencast.com/users/JamesMontemagno/folders/Jing/media/a844c283-550c-4647-82d3-32d8bda4282f/2016-07-11_1554.png)\n\nThis will take about 3-5 minutes to setup, so let's head back to the code!\n\n\n### Update AzureService.cs\nWe will use the [Azure Mobile Apps SDK](https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-xamarin-forms-get-started/) to connect our mobile app to our Azure back-end with just a few lines of code.\n\nOpen the Movies/Services/MobileAppMoviesSource.cs and add our url to the MobileAppUrl static field:\n\n```csharp\nprivate static readonly string MobileAppUrl = \"https://OUR-APP-NAME-HERE.azurewebsites.net\";\n```\n\nBe sure to update YOUR-APP-NAME-HERE with the app name you specified when creating your Azure Mobile App.\n\nThe logic in the Initialize method will setup our database and create our `IMobileServiceSyncTable\u003cMovie\u003e` table that we can use to retrieve Movie data from the Azure Mobile App. There are two methods that we need to fill in to get and sync data from the server.\n\n\n#### GetMovies\nIn this method, we will need to initialize, sync, and query the table for items. We can use complex LINQ queries to order the results:\n\n```csharp\nawait InitAndSync();\nawait _moviesTable.OrderBy(m =\u003e m.Genre).ToListAsync();\n```\n\n#### SyncMovies\nOur Azure backend can push any local changes and then pull all of the latest data from the server using the following code that can be added to the try inside of the SyncMovies method:\n\n```csharp\nReadOnlyCollection\u003cMobileServiceTableOperationError\u003e syncErrors = null;\ntry\n{\n    await _mobileServiceClient.SyncContext.PushAsync();\n}\ncatch (MobileServicePushFailedException e)\n{\n    if (e.PushResult != null)\n    {\n        syncErrors = e.PushResult.Errors;\n    }\n}\n\n// Error/conflict handling.\nif (syncErrors != null)\n{\n    foreach (var error in syncErrors)\n    {\n        if (error.OperationKind == MobileServiceTableOperationKind.Update \u0026\u0026 error.Result != null)\n        {\n            // Revert to server's copy\n            await error.CancelAndUpdateItemAsync(error.Result);\n        }\n        else\n        {\n            // Discard local change\n            await error.CancelAndDiscardItemAsync();\n        }\n\n        Debug.WriteLine($\"Error executing sync operation. Item: {error.TableName} ({error.Item[\"id\"]}). Operation discarded.\");\n    }\n}\n\ntry\n{\n    await _moviesTable.PullAsync(\"allMovies\", _moviesTable.CreateQuery());\n}\ncatch (Exception )\n{\n    // It is ok if we are not connected\n}\n```\nThat is it for our Azure code! Just a few lines, and we are ready to pull the data from Azure.\n\nNow, we have implemented the code we need in our app! Amazing isn't it?The AzureService object will automatically handle all communication with your Azure back-end for you, do online/offline synchronization so your app works even when it's not connected.\n\nLet's head back to the Azure Portal and populate the database.\n\nWhen the Quickstart finishes you should see the following screen, or can go to it by tapping the pin on the dashboard:\n\n![Quickstart](http://content.screencast.com/users/JamesMontemagno/folders/Jing/media/71ad3e06-dcc5-4c8b-8ebd-93b2df9ea2b2/2016-07-11_1601.png)\n\nUnder **Features** select **Easy Tables**.\n\nIt will have created a `TodoItem`, which you should see, but we can create a new table and upload a default set of data by selecting **Add from CSV** from the menu.\n\nEnsure that you have downloaded this repo and have the **Movies.csv** file that is in this folder.\n\nSelect the file and it will add a new table name and find the fields that we have listed. Then hit Start Upload.\n\n![upload data](http://content.screencast.com/users/JamesMontemagno/folders/Jing/media/eea2bca6-2dd0-45b3-99af-699d14a0113c/2016-07-11_1603.png)\n\nNow you can re-run your application and get data from Azure!\n\n\n## Take Home Challenges\n\n### Challenge 1: Text to Speech service\n\nIn this challenge you will add a new feature in our Movie app, that is Text to Speech, a service that is provided in every major platform.\nHowever, the Xamarin.Forms framework does not expose such abstracted service and to carry out this challenge you will have to provide plarform specific implementation.\n\n1.) In **MoviePage.cs** Summary page, add a ToolbarItem button and bind it to the MoviePageViewModel TextToSpeechCommand.\n\n2.) Initialize **TextToSpeechCommand** action by calling the ITextToSpeech interface service. You will notice that there are no services implementing such interface. As a matter of fact, you will have to create a TextToSpeechNative service in each platform-specific project and inject it through the Xamarin.Forms DependencyService.\nAs a reference, have a look at [DependencyService](https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/dependency-service/introduction/).\n\n### Challenge 2: Add a MovieAddPage\n\nIn this challenge you will create a form for adding a new Movie to the list of synchronized Movies of the app. In fact, you may have noticed that the AddMoviePage is currenly empty. The corresponding ViewModel is instead ready to be data bounded.\n\n1.) In **DetailPage.cs**, add a new ToolbarItem button to navigate to the **AddMoviePage** (assets provided)\n\n2.) Add all the textbox needed to store a new Movie:\n- Title\n- Year\n- Director\n- Country\n- Poster\n- Rating\n- Genre\n- Plot\n\nFor each of these visual elements, you will have to hook up its two-way binding.\n\n3.) Add a Save ToolbarItem button (assets provided)\n\nHappy coding!","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrancedot%2Fcnhindustrial-xamarindemo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffrancedot%2Fcnhindustrial-xamarindemo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrancedot%2Fcnhindustrial-xamarindemo/lists"}