MAUI: Passing Data Across Pages

.NET Multi-platform App UI (.NET MAUI) is a framework for building modern, multi-platform, natively compiled iOS, Android, macOS, and Windows apps using C# and XAML in a single codebase. This uses XAML for building an interactive User Interface.  In this article, we will build the application where we will see how to navigate across pages and share data across pages. 

The .NET MAUI is for developers who are interested in writing C# and XAML-based cross-platform applications. This helps to share UI layout and design with the same shared codebase across platforms. Figure 1 shows  the MAUI apps' code-sharing



Figure 1: The MAUI Apps Code Sharing

Figure 1 shows that the MAUI allows us to build cross-platform apps for desktop and mobile. More information can be read from this link.

In this article, we will see the implementation of navigation across pages and passing data across them. The MAUI provides several controls (aka UI elements) for creating application UI using XAML and these controls provide various events using which we can interact with them. We can show data in these controls using Data Binding features. If you have worked with Windows Presentation Foundation (WPF), then you know the greatness of using Data Binding using the Binding class.

To develop an application for this article I have used Visual Studio 2022 for Mac. Figure 2 shows the .NET MAUI project templates



Figure 2: MAUI Project templates

Step 1: As shown in Figure 2, create a new .NET MAUI project and name it MyMauiApp. Carefully, see the project, there you will see the following three Xaml file  

  • MainPage.xaml:
    • This file is a default UI file that will be loaded when the application is run.
    • This is a ContentPage to show UI.
  • AppShell.xaml:
    • This file contains a ShellContent control object. This control represents a ContentPage. The MainPage will be loaded in this ShellContent.  The ShellContent has the ConentTemplate property to load the MainPage and the Route property of the ShellContent defines route navigation to the MainPage by accepting the class name of the MainPage.
    • This Xaml file contains an AppShell class. This class is derived from the Shell class. The Shell class is a Page that is used to provide fundamental UI features. This class consists of the 3 hierarchical objects which are FlyoutItem, Tab, and ShellContent. These objects are not representing any UI instead they represent the Visual hierarchy of the user interface.
  • App.xaml:
    • This is an App class. This contains a MainPage property that is the root page of the application. 
    • If you open the App.xaml.cs, you will see that the MainPage property is set to the AppShell class instance so the UI can be loaded. 

Step 2: In the project add a new folder and name it Models. In this folder add a new class file and name it Models.cs. In this file add Department and Employee classes with it data as shown in Listing 1

using System;
using System.Collections.ObjectModel;

namespace MyMauiApp.Models
{
    public class Department
    {
        public int DeptNo { get; set; }
        public string DeptName { get; set; }
    }

    public class DepartmentList : ObservableCollection<Department>
    {
        public DepartmentList()
        {
            Add(new Department() { DeptNo = 10, DeptName = "Information Technology"});
            Add(new Department() { DeptNo = 20, DeptName = "Human Resource" });
            Add(new Department() { DeptNo = 30, DeptName = "Administration" });
            Add(new Department() { DeptNo = 40, DeptName = "Maintenence" });
            Add(new Department() { DeptNo = 50, DeptName = "Transport" });
        }
    }

    public class Employee
    {
        public int EmpNo { get; set; }
        public string EmpName { get; set; }
        public int Salary { get; set; }
        public decimal Tax { get; set; }
        public int DeptNo { get; set; }
    }
    public static class ProcessTax
    {
        public static Employee CalculateTax(Employee emp)
        {
            System.Threading.Thread.Sleep(100);
            emp.Tax = emp.Salary * Convert.ToDecimal(0.4);
            return emp;
        }
    }
    public class EmployeeList : ObservableCollection<Employee>
    {
        public EmployeeList()
        {
            Add(new Employee() { EmpNo = 1, EmpName = "Mahesh", Salary = 21000,DeptNo=10 });
            Add(new Employee() { EmpNo = 2, EmpName = "Tejas", Salary = 22000, DeptNo = 20 });
            Add(new Employee() { EmpNo = 3, EmpName = "Ramesh", Salary = 23000, DeptNo = 30 });
            Add(new Employee() { EmpNo = 4, EmpName = "Ram", Salary = 24000, DeptNo = 40 });
            Add(new Employee() { EmpNo = 5, EmpName = "Shankar", Salary = 25000, DeptNo = 50 });
            Add(new Employee() { EmpNo = 6, EmpName = "Vivek", Salary = 26000, DeptNo = 10 });
            Add(new Employee() { EmpNo = 7, EmpName = "Satish", Salary = 27000, DeptNo = 20 });
            Add(new Employee() { EmpNo = 8, EmpName = "Mukesh", Salary = 28000, DeptNo = 30 });
            Add(new Employee() { EmpNo = 9, EmpName = "Sandeep", Salary = 29000, DeptNo = 40 });
            Add(new Employee() { EmpNo = 10, EmpName = "Vinay", Salary = 30000, DeptNo = 50 });
            Add(new Employee() { EmpNo = 11, EmpName = "Sharad", Salary = 31000, DeptNo = 10 });
            Add(new Employee() { EmpNo = 12, EmpName = "Sanjay", Salary = 32000, DeptNo = 20 });
            Add(new Employee() { EmpNo = 13, EmpName = "Vijay", Salary = 33000, DeptNo = 30 });
            Add(new Employee() { EmpNo = 14, EmpName = "Vilas", Salary = 34000, DeptNo = 40 });
            Add(new Employee() { EmpNo = 15, EmpName = "Abhay", Salary = 35000, DeptNo = 50 });
            Add(new Employee() { EmpNo = 16, EmpName = "Nandu", Salary = 36000, DeptNo = 10 });
            Add(new Employee() { EmpNo = 17, EmpName = "Anil", Salary = 37000, DeptNo = 20 });
            Add(new Employee() { EmpNo = 18, EmpName = "Jaywant", Salary = 38000, DeptNo = 30 });
            Add(new Employee() { EmpNo = 19, EmpName = "Abhay", Salary = 39000, DeptNo = 40 });
            Add(new Employee() { EmpNo = 20, EmpName = "Shyam", Salary = 40000, DeptNo = 50 });
            Add(new Employee() { EmpNo = 21, EmpName = "Anil", Salary = 41000, DeptNo = 10 });
            Add(new Employee() { EmpNo = 22, EmpName = "Vasant", Salary = 42000, DeptNo = 20 });
            Add(new Employee() { EmpNo = 23, EmpName = "Sameer", Salary = 43000, DeptNo = 30 });
            Add(new Employee() { EmpNo = 24, EmpName = "Rahul", Salary = 44000, DeptNo = 40 });
            Add(new Employee() { EmpNo = 25, EmpName = "Abhishek", Salary = 45000, DeptNo = 50 });
            Add(new Employee() { EmpNo = 26, EmpName = "Kaushubh", Salary = 46000, DeptNo = 10 });

        }
    }
}


Listing 1: Model classes

Step 3: In the project add a new .NET MAUI ContentPage and name it Departments.xaml as shown in Figure 3



Figure 3: Adding a new ContentPage   

Similarly, in the project add a new folder and name it Views. In this folder add a new content page and name it Employees.xaml. 

Step 4: Since we will be navigating to Employees.xaml from Departments.xaml, we need to create Shell navigation using Routing. This class has a method RegisterRoute() method. The first parameter of this method is the route link which is a string and the second parameter is the ContentPage Type name (aka the ContentPage class) to navigate to. Open AppShell.xaml.cs and add the Route as shown in Listing 2


public partial class AppShell : Shell
{
	public AppShell()
	{
		InitializeComponent();

		Routing.RegisterRoute("Employees", typeof(MyMauiApp.Views.Employees));
    }
}

Listing 2: The Routing Configuration    

Step 5: In the Departents.xaml, add a ListView, inside the VerticleStackLayout control. In this ListView, we will be showing Department information. Each row from the DepartmentsList collection will be shown as an Item inside the ListView using the ItemTemplate property of it. The ItemSelected event of the ListView will be raised when the Item is selected from the ListView. The ItemsSource property of the ListView is used to show DepartmentsList data in it. Listing 3, shows the XAML UI


  <ListView x:Name="lstDepartments"  
                   VerticalScrollBarVisibility="Always"
                  HorizontalScrollBarVisibility="Always"
                  ItemsSource="{Binding DepartmentsList}"
                 ItemSelected="lstDepartments_ItemSelected"
                   HeightRequest="300">
            <ListView.ItemTemplate>
                <DataTemplate>

                    <ViewCell>
                        <StackLayout Orientation="Horizontal">
                            <Label Text="{Binding DeptNo}"  WidthRequest="200"/>
                             <Label Text="{Binding DeptName}"  WidthRequest="200"/>
                             
                        </StackLayout>
                          
                          
                    </ViewCell>

                </DataTemplate>

            </ListView.ItemTemplate>

        </ListView>

Listing 3: The XAML UI for Departments.xaml

Listing 3, shows the ListView's ItemTemplate containing a DataTemplate to display Department details using the Labels rendered inside the StackLayout. DataTemplate is a Visual Tree that will be rendered based on the Data bound with the UI element. In our case, the ListView is bound with the DepartmentsList using Binding.  Let's modify the Departments.xaml.cs where we will define DepartmentsList property sho bind data with ListView and the ItemsSelected event as shown in Listing 4


namespace MyMauiApp;
using MyMauiApp.Models;
 

public partial class Departments : ContentPage
{
    public DepartmentList DepartmentsList { get; set; }
    public Departments()
    {

        InitializeComponent();
        DepartmentsList = new DepartmentList();
        this.BindingContext = this;
    }
    /// <summary>
    /// The Selected Item Event
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    async void lstDepartments_ItemSelected(System.Object sender, 
        Microsoft.Maui.Controls.SelectedItemChangedEventArgs e)
    {
        // Get the selected Department from the ListItem
        var dept = (Department)lstDepartments.SelectedItem;
        // Create a Navigation Parameter using the Dictionary
        var navigationParameter = new Dictionary<string, object>
    {
        { "SelectedDepartment",  dept}
    };

        // Navigate to the Employees Route with the Navigation Parameter
        await Shell.Current.GoToAsync($"Employees", navigationParameter);
    }
}

Listing 4: The Departments.xaml.cs

As shown in Listing 4, the DepartmentsList is defined as a public property of the type DepartmentList class which we have defined in the Models.cs. The constructor binds all public properties defined in the code using the BindingContext property of the type BindableObject. This means that data in the public properties of Departments.xaml.cs will be bindable properties so that we can bind them with XAML UI elements. The lstDepartments_ItemSelected method will be invoked with the ItemSelected event of the ListView. Since the ListView is bound with the DepartmentsList using its ItemsSource property, each item of the ListView will be the Department object. This method reads the SelectedItem from the ListView and stores it in the dept object by casting it. This method further defines a Dictionary object that contains the key as SelectedDepartment and the value as the selected Department. We use this Dictionary object to pass as a navigation parameter to the GoToAsync() method of the Shell navigation so that we can navigate to the Employees ContentPage. 

So the important steps here are creating a Routing and Navigating to the ContentPage using the Shell Navigation along with the parameter. The next challenge is to read the navigation parameter data on the target page. 

Step 6: Open Employees.xaml.cs and add the code in it as shown in Listing 5


using System.Collections.ObjectModel;
using MyMauiApp.Models;

namespace MyMauiApp.Views;

public partial class Employees : ContentPage,IQueryAttributable
{
    public ObservableCollection<Employee> EmployeesList { get; set; }
    public string DeptName { get; set; }
    public Employees()
	{
		InitializeComponent();
		EmployeesList = new EmployeeList();
        this.BindingContext = this;
        OnPropertyChanged();
	}

    public void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        var dept = query["SelectedDepartment"] as Department;
       
        DeptName = dept.DeptName;

        var Emps = new EmployeeList(); 
        for (int i = 0; i < Emps.Count; i++)
        {
            Emps[i] = ProcessTax.CalculateTax(EmployeesList[i]);
        }
var filteredData = Emps.Where(e => e.DeptNo == dept.DeptNo); EmployeesList.Clear(); foreach (var emp in filteredData) { EmployeesList.Add(emp); } OnPropertyChanged(); } }

Listing 5: The Employees.xaml.cs to read the navigation data

The most important part of the Employees class in Listing 5 is the IQueryAttributable interface. This interface must be implemented by the receiving class to receive navigation data passed by the sender using Route based shell Navigation. The receiving class must implement the ApplyQueryAttributes() method. This method accepts a query argument, of type IDictionary<string, object>, that contains any data passed during navigation. Each key in this dictionary is a query parameter id, with its value corresponding to the object that represents the data. The advantage of using this approach is that navigation data can be processed using a single method, which can be useful when you have multiple items of navigation data that require processing as a whole.  In our case the navigation parameter name is SelectedDepartment. This carries the selected Department from the Departments ContentPage and then filters all Employees from the EmployeeList class which is defined in Models.cs. All these employees will be added EmployeeList public property of the type ObservableCollection<Employee>. The ObservableCollection is the most powerful class that will notify any changes that occurred in the collection and if this collection is bound to the UI then the UI will be updated with updated values. 

To define UI for the Employees, add a Label and a ListView in the XAML in Employees.xaml as shown in Listing 6


<Label 
            Text="{Binding DeptName}"
            VerticalOptions="Center"
            FontSize="Header"
            HorizontalOptions="Center" />
          <ListView x:Name="lstEmpData"  
                   VerticalScrollBarVisibility="Always"
                  HorizontalScrollBarVisibility="Always"
                   HeightRequest="300"
                    ItemsSource="{Binding EmployeesList}">
            <ListView.ItemTemplate>
                <DataTemplate>

                    <ViewCell>
                        <StackLayout Orientation="Horizontal">
                            <Label Text="{Binding EmpNo}"  WidthRequest="80"/>
                             <Label Text="{Binding EmpName}"  WidthRequest="300"/>
                             <Label Text="{Binding Salary}"  WidthRequest="200"/>
                             <Label Text="{Binding Tax}"  WidthRequest="200"/>
                        </StackLayout>
                          
                          
                    </ViewCell>

                </DataTemplate>

            </ListView.ItemTemplate>

        </ListView>

Listing 6: Employees.xaml 

As shown in Listing 6, the ListView is bound with the EmployeeList property that is containing the filtered Employees based on the selected Department from the Departments view.  The Employee data will be displayed in the ListView using the ItemTemplate property of the ListView.


Run the Application by selecting the My Mac as shown in Figure 4



Figure 4: Selecting the platform for running 

Run the application, and the application will be loaded and display Departments List as shown in Figure 5



Figure 5: Departments List

Select a Department from the List, for the Selected Department Employees will be displayed  as shown in Figure 6



Figure 6: List of Employees

You can also test this on the Apple Devive Simulator as shown in Figure 7



Figure 7: Using The Apple Simulator

The code for this article can be downloaded from this link.    

Conclusion: The .NET MAUI is one of the best technology for building multi-platform apps.    Using the XAML the interactive code can be easily written. The Binding feature and Navigation feature helps to build a Single-Page-Interactive application.            

    


 


Comments

Popular posts from this blog

ASP.NET Core 6: Adding Custom Middleware and Logging the Error Message in Database

ASP.NET Core 6: Downloading Files from the Server

ASP.NET Core 6: Using Role Based Security for ASP.NET Core 6 WEB API