DEV Community

Cover image for Build a Stunning Book Library App with .NET MAUI ListView – Fast, Flexible & Cross-Platform
Calvince Moth for Syncfusion, Inc.

Posted on • Originally published at syncfusion.com on

Build a Stunning Book Library App with .NET MAUI ListView – Fast, Flexible & Cross-Platform

TL;DR: Learn how to build a powerful cross-platform .NET MAUI Book Library App using ListView and DataForm. This guide walks you through managing book collections with full CRUD operations, intuitive UI controls, and smart filtering, all from a single codebase for Android and iOS.

.NET MAUI simplifies cross-platform development, allowing you to build apps for Android and iOS with a single codebase. Its ListView control is perfect for displaying book collections, while DataForm streamlines editing and viewing book details. This tutorial focuses on using these controls (with Syncfusion® enhanced versions) to create a feature-rich book management app.

Note: This guide uses Syncfusion® ListView and DataForm for advanced features . Install the Syncfusion.Maui.ListView and Syncfusion.Maui.DataForm NuGet packages or use the standard .NET MAUI ListView with minor adjustments

ListView is a versatile control in .NET MAUI for presenting lists of data. It supports features like data binding, item templates, and item selection, making it ideal for managing a collection of books in our library app. Here we have the sections to handle.

Displaying books with ListView

One of the most critical features when building a book library app is presenting book information in a clean and organized manner. ListView is a versatile control that simplifies the process of displaying and managing lists, making it an ideal choice for showcasing books.

Here, we have two steps to display books in a ListView.

  • Implementing Model and ViewModel
  • Designing the UI

Step 1: Implementing Model and ViewModel

First, include the Model class to handle the book info properties and the view model class to populate the binding collection property to show the books list in the ListView.

Creating a model

Create a data model to bind it to the control. Create a simple data source in a new class file and save it as BookInfo.cs file, as shown in the code example below.

public class BookInfo : INotifyPropertyChanged
{
    #region Fields

    private string name;
    private string desc;
    private string author;
    private string image;

    #endregion

    #region Constructor

    public BookInfo()
    {

    }

    #endregion

    #region Properties

    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            OnPropertyChanged("Name");
        }
    }

    public string Description
    {
        get { return desc; }
        set
        {
            desc = value;
            OnPropertyChanged("Description");
        }
    }

    public string Author
    {
        get { return author; }
        set
        {
            author = value;
            OnPropertyChanged("Author");
        }
    }

    [Display(AutoGenerateField = false)]
    public string Image
    {
        get { return image; }
        set
        {
            image = value;
            OnPropertyChanged("Image");
        }
    }

    #endregion

    #region Interface Member

    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string name)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(name));
    }            
    #endregion
}

Enter fullscreen mode Exit fullscreen mode

Creating a view model

Create a model repository class with BookInfo collection property, initialized with the required number of data objects in a new class file and save it as a ViewModel.cs file, as shown in the code example below.

public class ViewModel : INotifyPropertyChanged
{
    #region Fields

    private ObservableCollection bookInfo;

    #endregion

    #region Constructor

    public ViewModel()
    {
        GenerateSource();
    }

    #endregion

    #region Properties

    public ObservableCollection Books
    {
        get { return bookInfo; }
        set { bookInfo = value; }
    }
}

Enter fullscreen mode Exit fullscreen mode

The list of Books is now ready to be bound and shown in a ListView. We can design the UI now.

Step 2: Designing the UI

Now, we will create the UI to show books using the .NET MAUI ListView. Install the necessary package to use the control in the application.

Note: To know more about ListView, please refer to the .NET MAUI ListView documentation.

Binding data to the listview

The BindingContext for the main page is set with a ViewModel to bind properties and commands to the ListView.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:syncfusion="clr-namespace:Syncfusion.Maui.ListView;assembly=Syncfusion.Maui.ListView"
             xmlns:local="clr-namespace:ListViewMAUI"
             x:Class="ListViewMAUI.MainPage">

    <ContentPage.BindingContext>
              <local:ViewModel/>
    </ContentPage.BindingContext>

</ContentPage>

Enter fullscreen mode Exit fullscreen mode

To populate the ListView, bind the item collection from the BindingContext to the SfListView.ItemsSource property.

The code example below demonstrates how to bind the Books collection to the SfListView.ItemsSource property:

<syncfusion:SfListView x:Name="listView" ItemsSource="{Binding Books}"/>

Enter fullscreen mode Exit fullscreen mode

Defining the item template

To define the ItemTemplate for a ListView, you can create a custom template with controls like labels for displaying book details and an image to show the book cover.

<syncfusion:SfListView x:Name="listView"
        AutoFitMode="Height"
        ItemSize="200"           
        Grid.Row="1"
        TapCommand="{Binding TapCommand}"
        ItemsSource="{Binding Books}">

    <syncfusion:SfListView.ItemTemplate>
        <DataTemplate>
            <Grid RowSpacing="0" Padding="8,12,8,0" ColumnSpacing="0" Margin="0">

                    <Image Source="{Binding Image}"
                           Grid.Column="0"
                           Grid.Row="0"
                           HeightRequest="100"
                           WidthRequest="90"
                           HorizontalOptions="Start"
                           VerticalOptions="Start" />
                    <StackLayout Orientation="Vertical" VerticalOptions="Start" Grid.Row="0" Grid.Column="1"
                                Padding='{OnPlatform Default="5,-5,0,0"}'>
                        <Label Text="{Binding Name}" FontAttributes="Bold" FontSize="16" TextColor="#474747"/>
                        <Label Text="{Binding Author}" Grid.Row="1" FontSize="14" Opacity=" 0.67" TextColor="#474747" />
                        <Label Text="{Binding Description}" Opacity=" 0.54" TextColor="#474747" FontSize="13"/>
                    </StackLayout>

                <BoxView Grid.Row="1" Margin="5,0,0,0" HeightRequest="1" Opacity="0.75" BackgroundColor="#CECECE" />

            </Grid>
        </DataTemplate>
    </syncfusion:SfListView.ItemTemplate>
</syncfusion:SfListView>

Enter fullscreen mode Exit fullscreen mode

You will get the following output with the above code snippet,

<alt-text>


Displaying books using ListView ItemTemplate

Display book details in DataForm

DataForm is used to display the book using a DataObject bound to the ViewModel’s SelectedItem property. The SelectedItem object will be updated when you tap the books.

<dataForm:SfDataForm x:Name="bookForm" 
                      DataObject="{Binding SelectedItem}" 
                      Grid.Row="1" 
                      IsReadOnly="{Binding IsReadOnly}" 
                      CommitMode="Manual" />

Enter fullscreen mode Exit fullscreen mode

The IsReadOnly property is used here to determine whether we are showing the book alone or trying to edit the book.

public class ViewModel : INotifyPropertyChanged
{
    #region Fields

    private BookInfo selectBook;

    private bool isReadOnly;

    #endregion

    #region Constructor

    public ViewModel()
    {
        InitializeCommands();            
    }

    #endregion

    #region Properties

    public BookInfo SelectedItem
    {
        get
        {
            return selectBook;
        }
        set
        {
            selectBook = value;
            OnPropertyChanged(nameof(SelectedItem));
        }
    }

    public bool IsReadOnly
    {
        get
        {
            return isReadOnly;
        }
        set
        {
            isReadOnly = value;

            OnPropertyChanged(nameof(IsReadOnly));
            OnPropertyChanged(nameof(IsVisible));
        }
    }

    public Command TapCommand { get; set; }

    #endregion

    private void InitializeCommands()
    {
        TapCommand = new Command(OnTapCommand);
    }

    private async void OnTapCommand(object eventArgs)
    {
        var tappedEventArgs = eventArgs as Syncfusion.Maui.ListView.ItemTappedEventArgs;
        if (tappedEventArgs != null)
        {
            SelectedItem = tappedEventArgs.DataItem as BookInfo;
            if (SelectedItem == null)
                return;
            IsReadOnly = true;
            var editPage = new BookPage();
            editPage.BindingContext = this;
            await App.Current.MainPage.Navigation.PushAsync(editPage);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

When you tap the book, you can see detailed information about it. You will get the following output when you run the above code snippet.

<alt-text>


Opening book details in DataForm

CRUD operations with .NET MAUI DataForm

We will perform CRUD operations on the Books collection using .NET MAUI DataForm. Use the add icon and create a book entry using the DataForm with the bound property SelectedItem from the ViewModel. The new Book was created using the CreatedBookCommand.

public class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
    {
        InitializeCommands();           
     }

    #endregion

    #region Properties



    public Command CreateBookCommand { get; set; }
    public Command SaveBookCommand { get; set; }
    public Command CancelBookCommand { get; set; }
    public Command EditBookCommand { get; set; }
    public Command DeleteBookCommand { get; set; }

    #endregion
    private void InitializeCommands()
    {
        CreateBookCommand = new Command(OnCreateBook);
        SaveBookCommand = new Command(OnSaveBook);
        CancelBookCommand = new Command(OnCancelBook);
        EditBookCommand = new Command(OnEditBookCommand);
        DeleteBookCommand = new Command(OnDeleteBookCommand);
    }

    internal async void OnCreateBook()
    {
        SelectedItem = new BookInfo();
        IsReadOnly = false;
        var editPage = new BookPage();
        editPage.BindingContext = this;
        await App.Current.MainPage.Navigation.PushAsync(editPage);
    }

    internal async void OnCancelBook()
    {
        SelectedItem = null;
        await App.Current.MainPage.Navigation.PopAsync();
    }

    internal async void OnSaveBook()
    {
        if (!Books.Contains(SelectedItem))
        {
            Books.Add(SelectedItem);
        }            
        await App.Current.MainPage.Navigation.PopAsync();
    }


    internal void OnEditBookCommand()
    {
        IsReadOnly = false;
    }

    internal async void OnDeleteBookCommand()
    {
        Books.Remove(SelectedItem);
        await App.Current.MainPage.Navigation.PopAsync();
    }    
}

Enter fullscreen mode Exit fullscreen mode

Adding book

The add icon is defined using a Label and a font icon. ViewModel CreateBookCommand is assigned in Label gestures to create a new Book and show the fields in DataForm to add the values.

<Label Text=""           
        VerticalOptions="Center"
        HorizontalOptions="Center"
FontFamily="MauiSampleFontIcon"          
TextColor="{StaticResource Primary}"        
    Grid.Column="1"
FontSize="Medium" >
    <Label.GestureRecognizers>
        <TapGestureRecognizer Command="{Binding CreateBookCommand}"/>
    </Label.GestureRecognizers>
</Label>

Enter fullscreen mode Exit fullscreen mode

You will see the following output when you click on the plus icon on the Books page.

<alt-text>


Adding a book detail in .NET MAUI ListView

On this page, you can add the new book to the list using the save button or discard the book using the cancel button.

Editing book

You can edit the book when you tap the information about the book. When you tap on each book, you can see the book options.

You can edit the book using the IsReadOnly property as false. An edited value can be committed only on save, as we defined the commit mode as Manual for DataForm. The value will be committed using the Commit method.

bookForm.Commit();
Enter fullscreen mode Exit fullscreen mode

Deleting book

The tapped book can be deleted using the delete icon in the DataForm page. The book will be deleted from the list.

These edit, save, cancel, and delete icons are defined using SfChipGroup. You can find the code snippets here.

<chip:SfChipGroup HorizontalOptions="Center" 
    IsVisible="{Binding IsVisible}"
                                ItemHeight="40" 
                                ChipCornerRadius="20" 
                                ChipBackground="Transparent" 
                                ChipStrokeThickness="0"      
                                ChipPadding="10"  
                                ChipClicked="OnChipClicked"                                                                               
                                ItemsSource="{Binding CommitOptions}">
    <chip:SfChipGroup.ItemTemplate>
        <DataTemplate>
            <HorizontalStackLayout>
                <Label Text="{Binding ActionIcon}" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" WidthRequest="40" FontFamily="MauiSampleFontIcon" TextColor="{StaticResource Primary}" FontSize="Medium"/>
                <Label Text="{Binding ActionName}" VerticalTextAlignment="Center" HorizontalTextAlignment="Start" TextColor="{StaticResource Primary}" FontFamily="Roboto-Regular" FontSize="{OnPlatform Default=16,WinUI=14}" Margin="0,0,5,0"/>
            </HorizontalStackLayout>
        </DataTemplate>
    </chip:SfChipGroup.ItemTemplate>
</chip:SfChipGroup>

Enter fullscreen mode Exit fullscreen mode

Based on the chip clicked, the associated action will be performed

private void OnChipClicked(object sender, EventArgs e)
{
    var viewmodel = this.BindingContext as ViewModel;
    var chip = (sender as SfChip);
    var layout = chip.Children[0] as HorizontalStackLayout;
    var action = (layout.BindingContext as BookOption).ActionName;
    if (string.IsNullOrEmpty(action))
        return;

    switch(action)
    {
        case "Edit":
            viewmodel.OnEditBookCommand();
            break;
        case "Delete":
            viewmodel.OnDeleteBookCommand();
            break;
        case "Save":
            viewmodel.OnSaveBook();
            bookForm.Commit();
            break;
        case "Cancel":
            viewmodel.OnCancelBook();
            break;
    }
}

Enter fullscreen mode Exit fullscreen mode

Add filtering

Creating a book library app isn’t just about functionality; it’s about delivering a seamless and intuitive experience that makes managing books effortless and enjoyable. A well-thought-out user experience can significantly improve user satisfaction and engagement. Here’s how to enhance your book library app.

Filtering is applied to the text changed event of the SearchTextBox.

public class ListViewSearchBehavior : Behavior<ContentPage>
{
    #region Fields

    private Syncfusion.Maui.ListView.SfListView ListView;
    private SearchBar searchBar = null;

    #endregion

    #region Overrides
    protected override void OnAttachedTo(ContentPage bindable)
    {
        ListView = ListView = bindable.FindByName<Syncfusion.Maui.ListView.SfListView>("listView");
        searchBar = bindable.FindByName<SearchBar>("searchBar");
        searchBar.TextChanged += SearchBar_TextChanged;

        base.OnAttachedTo(bindable);
    }

    protected override void OnDetachingFrom(ContentPage bindable)
    {
        ListView = null;
        searchBar = null;
        searchBar.TextChanged -= SearchBar_TextChanged;
        base.OnDetachingFrom(bindable);
    }

    private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
    {
        searchBar = (sender as SearchBar);
        if (ListView.DataSource != null)
        {
            ListView.DataSource.Filter = FilterBooks;
            ListView.DataSource.RefreshFilter();
        }
        ListView.RefreshView();
    }

    private bool FilterBooks(object obj)
    {
        if (searchBar == null || searchBar.Text == null)
            return true;

        var bookInfo = obj as BookInfo;
        return (bookInfo.Name.ToLower().Contains(searchBar.Text.ToLower()) || 
                (bookInfo.Description.ToString()).ToLower().Contains(searchBar.Text.ToLower()));
    }

    #endregion
}
Enter fullscreen mode Exit fullscreen mode

Behavior class added to content page. You can find the code snippet here.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:syncfusion="clr-namespace:Syncfusion.Maui.ListView;assembly=Syncfusion.Maui.ListView"
             xmlns:chip="clr-namespace:Syncfusion.Maui.Core;assembly=Syncfusion.Maui.Core"
             xmlns:local="clr-namespace:ListViewMAUI"
             x:Class="ListViewMAUI.MainPage">

    <ContentPage.Behaviors>
        <local:ListViewSearchBehavior/>
    </ContentPage.Behaviors>
</ContentPage>

Enter fullscreen mode Exit fullscreen mode

You can find the output below.

<alt-text>


Adding a Filter in the app

GitHub reference

For more details, refer to the GitHub demo.

FAQs

Q1: What makes Syncfusion controls special for this Book Library App?

Syncfusion’s SfListView and SfDataForm are enhanced versions of standard .NET MAUI controls. They provide advanced features and streamline development for the Book Library App, though you can use standard controls with minor adjustments.

Q2: What makes this Book Library App user-friendly?

The app focuses on a seamless and intuitive experience through:

– Organized book display using ListView templates

– Filtering capabilities based on text input

– Easy book management that’s both effortless and enjoyable

Q3: How does the app handle user actions like tapping books or clicking buttons?

User interactions are managed through Commands in the ViewModel:

Tapping a book: Triggers TapCommand to select the book and navigate to details

Action buttons: Use SfChipGroup clicks to execute commands like SaveBookCommand, EditBookCommand, or DeleteBookCommand.

Q4: Why is INotifyPropertyChanged important for data binding?

INotifyPropertyChanged ensures the UI automatically updates when data changes. Both the BookInfo model and ViewModel use this interface so that when properties change (like selecting a new book), the interface refreshes automatically through OnPropertyChanged.

Conclusion

In this blog post, we’ve explored how to create a cross-platform book library app using .NET MAUI, focusing on leveraging the ListView control to efficiently manage and display book collections. We’ve walked through the process of designing a model and view model to handle data, binding a collection of books to the ListView, and customizing the user interface for optimal display.

If you’re already a Syncfusion® user, you can download the product setup from the license and downloads page. Otherwise, you can download a free 30-day trial.

Please let us know in the comments section below if you have any questions. You can also contact us through our support forum, support portal, or feedback portal. We are always happy to assist you!

Related Blogs

This article was originally published at Syncfusion.com.

Sentry image

Make it make sense

Only get the information you need to fix your code that’s broken with Sentry.

Start debugging →

Top comments (0)

Developer-first embedded dashboards

Developer-first embedded dashboards

Embed in minutes, load in milliseconds, extend infinitely. Import any chart, connect to any database, embed anywhere. Scale elegantly, monitor effortlessly, CI/CD & version control.

Get early access

👋 Kindness is contagious

Explore this practical breakdown on DEV’s open platform, where developers from every background come together to push boundaries. No matter your experience, your viewpoint enriches the conversation.

Dropping a simple “thank you” or question in the comments goes a long way in supporting authors—your feedback helps ideas evolve.

At DEV, shared discovery drives progress and builds lasting bonds. If this post resonated, a quick nod of appreciation can make all the difference.

Okay