DEV Community 👩‍💻👨‍💻

Niroj Dahal
Niroj Dahal

Posted on

Xamarin Forms- Let's Build Web-like Pagination

I was working with more than 1000 rows and had to implement pagination in Xamarin Forms. I made use of ContentView and made it reusable. We will be using FontAwesome icons to add icons to our navigation button in pager. Let's dive into building it from scratch

Getting Started

Install Xamarin.CommunityToolkit nuget package from Nuget Package Manager in all projects of a solution . This package is used to implement EventToCommandBehaviour and asynchronous Command for Pagination View.

Building Pagination View

  1. Right Click on Shared Project and Add ContentView (xaml) and name it to PaginationView.
  2. To add FontAwesome icons to our project , visit the link and download it. Add downloaded files to our Shared project and set properties of those files as EmbeddedResource. Export font in Assembly level by adding below line before namespace of PaginationView > [assembly: ExportFont("FontAwesomeSolid.otf", Alias = "FontAwesomeSolid")]
  3. In order to make this view reusable, we will declare Foreground Color of Navigation buttons, Disabled Color, Current active page and Total Number of pages as BindableProperty. Let's get this done in Code-behind of PaginationView

```public static readonly BindableProperty CurrentPageProperty = BindableProperty.Create(nameof(CurrentPage), typeof(int), typeof(PaginationView), defaultValue: 1, propertyChanged: CurrentPagePropertyChanged);

    public static readonly BindableProperty PageCountProperty = BindableProperty.Create(nameof(PageCount), typeof(int), typeof(PaginationView), defaultValue: 1, propertyChanged: PageCountPropertyChanged);

    public static readonly BindableProperty DisabledColorProperty = BindableProperty.Create(nameof(DisabledColor), typeof(Color), typeof(PaginationView), defaultValue: Color.Gray, propertyChanged: OnDisabledColorPropertyChanged);


    public static readonly BindableProperty IconBackgroundColorProperty = BindableProperty.Create(nameof(IconBackgroundColor), typeof(Color), typeof(PaginationView), defaultValue: Color.Green, propertyChanged: IconBackgroundColorPropertyChanged);

   public static readonly BindableProperty OnPaginatedProperty = BindableProperty.Create(nameof(OnPaginated), typeof(IAsyncCommand<int>), typeof(PaginationView));

   private static void OnDisabledColorPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((PaginationView)bindable).SetPageNavigationValues();
    }
    private static void IconBackgroundColorPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((PaginationView)bindable).SetPageNavigationValues();
    }

    private static void PageCountPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((PaginationView)bindable).SetPageNavigationValues();
    }

    private static void CurrentPagePropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((PaginationView)bindable).SetPageNavigationValues();
    }
    public Color DisabledColor
    {
        get => (Color)GetValue(DisabledColorProperty);
        set => SetValue(DisabledColorProperty, value);
    }

    public Color IconBackgroundColor
    {
        get => (Color)GetValue(IconBackgroundColorProperty);
        set => SetValue(IconBackgroundColorProperty, value);
    }

    public int CurrentPage
    {
        get => (int)GetValue(CurrentPageProperty);
        set => SetValue(CurrentPageProperty, value);
    }

    public int PageCount
    {
        get => (int)GetValue(PageCountProperty);
        set => SetValue(PageCountProperty, value);
    }

    public IAsyncCommand<int> OnPaginated
    {
        get => (IAsyncCommand<int>)GetValue(OnPaginatedProperty);
        set => SetValue(OnPaginatedProperty, value);
    }```
Enter fullscreen mode Exit fullscreen mode

Here are a couple of things going on. First we declared all the properties that are bindable and we declared backing properties for them.

Backing field must have its name same as its bindable property without 'Property' text in it.

For Data binding, we need to implement INotifyPropertyChanged in our PaginationView.

We declared a bindable property named OnPaginatedProperty . This is of type IAsyncCommand and is a callback command in view that Uses Pagination view in it.

With bindable properties in place, we can now write our logic to manipulate our Pagination view

Whenever navigation reaches to first page, move to first page and navigate backwards buttons are disabled and when navigation reaches last page, move to last page and navigate forward buttons are disabled. We can achieve this logic by writing

  private async ValueTask GetLastPageData()
    {
        if (CurrentPage == PageCount)
            return;
        CurrentPage = PageCount;
        SetPageNavigationValues();
        await ExecuteCommand();
    }

    private async ValueTask GetNextPageData()
    {
        if (CurrentPage == PageCount)
            return;
        CurrentPage += 1;
        SetPageNavigationValues();
        await ExecuteCommand();
    }

    private async ValueTask GetPreviousPageData()
    {
        if (CurrentPage == 1)
            return;
        CurrentPage -= 1;
        SetPageNavigationValues();
        await ExecuteCommand();
    }

    private async ValueTask GetFirstPageData()
    {
        if (CurrentPage == 1)
            return;
        CurrentPage = 1;
        SetPageNavigationValues();
        await ExecuteCommand();
    }

    private async Task ExecuteCommand()
    {
        if (OnPaginated != null)
            await OnPaginated.ExecuteAsync(CurrentPage);
    }

    private bool _allowPreviousPageNavigation;
    public bool AllowPreviousPageNavigation
    {
        get => _allowPreviousPageNavigation;
        set
        {
            _allowPreviousPageNavigation = value;
            PreviousPageButtonForegroundColor = value ? IconBackgroundColor : DisabledColor;
            OnPropertyChanged(nameof(AllowPreviousPageNavigation));
        }
    }
    private bool _allowFirstPageNavigation;
    public bool AllowFirstPageNavigation
    {
        get => _allowFirstPageNavigation;
        set
        {
            _allowFirstPageNavigation = value;

            FirstPageButtonForegroundColor = value ? IconBackgroundColor : DisabledColor;
            OnPropertyChanged(nameof(AllowFirstPageNavigation));
        }
    }

    private bool _allowNextPageNavigation;
    public bool AllowNextPageNavigation
    {
        get => _allowNextPageNavigation;
        set
        {
            _allowNextPageNavigation = value;
            NextPageButtonForegroundColor = value ? IconBackgroundColor : DisabledColor;
            OnPropertyChanged(nameof(AllowNextPageNavigation));
        }
    }
    private bool _allowLastPageNavigation;
    public bool AllowLastPageNavigation
    {
        get => _allowLastPageNavigation;
        set
        {
            _allowLastPageNavigation = value;
            LastPageButtonForegroundColor = value ? IconBackgroundColor : DisabledColor;
            OnPropertyChanged(nameof(AllowLastPageNavigation));
        }
    }

    private void SetPageNavigationValues()
    {
        AllowFirstPageNavigation = true;
        AllowNextPageNavigation = true;
        AllowLastPageNavigation = true;
        AllowPreviousPageNavigation = true;
        if (CurrentPage == 1)
        {
            AllowPreviousPageNavigation = false;
            AllowFirstPageNavigation = false;
        }
        if (CurrentPage == PageCount)
        {
            AllowNextPageNavigation = false;
            AllowLastPageNavigation = false;
        }
    }
Enter fullscreen mode Exit fullscreen mode

Now we just need to bind these functions to Command that gets called from our Pagination Component when we click navigation button in it. This can be achieved by initialising our commands in constructor

   public IAsyncValueCommand MoveToFirstPageCommand { get; set; }
    public IAsyncValueCommand MoveToPreviousPageCommand { get; set; }
    public IAsyncValueCommand MoveToNextPageCommand { get; set; }
    public IAsyncValueCommand MoveToLastPageCommand { get; set; }

    public PaginationView()
    {
        InitializeComponent();
        MoveToFirstPageCommand = new AsyncValueCommand(() => GetFirstPageData(), allowsMultipleExecutions: false);
        MoveToPreviousPageCommand = new AsyncValueCommand(() => GetPreviousPageData(), allowsMultipleExecutions: false);
        MoveToNextPageCommand = new AsyncValueCommand(() => GetNextPageData(), allowsMultipleExecutions: false);
        MoveToLastPageCommand = new AsyncValueCommand(() => GetLastPageData(), allowsMultipleExecutions: false);
        BindingContext = this;
    }
Enter fullscreen mode Exit fullscreen mode

Now, lets get into design part of our ContentView.

    <?xml version="1.0" encoding="UTF-8"?>
   <ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         x:Class="Xam.Views.Pagination.PaginationView"
            xmlns:xct="http://xamarin.com/schemas/2020/toolkit">
<StackLayout Orientation="Horizontal" HorizontalOptions="CenterAndExpand" Margin="0,0,0,10">
    <ImageButton HeightRequest="40" BackgroundColor="Transparent" IsEnabled="{Binding AllowFirstPageNavigation}">
        <ImageButton.Source>
            <FontImageSource FontFamily="FontAwesomeSolid"
                  Color="{Binding FirstPageButtonForegroundColor}"
                 Glyph="&#xf100;"/>
        </ImageButton.Source>

        <ImageButton.Behaviors>
            <xct:EventToCommandBehavior EventName="Clicked" Command="{Binding MoveToFirstPageCommand}"></xct:EventToCommandBehavior>
        </ImageButton.Behaviors>
    </ImageButton>
    <ImageButton HeightRequest="40" BackgroundColor="Transparent" IsEnabled="{Binding AllowPreviousPageNavigation}">
        <ImageButton.Source>
            <FontImageSource FontFamily="FontAwesomeSolid"
                  Color="{Binding PreviousPageButtonForegroundColor}"
                 Glyph="&#xf104;"/>
        </ImageButton.Source>
        <ImageButton.Behaviors>
            <xct:EventToCommandBehavior EventName="Clicked" Command="{Binding MoveToPreviousPageCommand}"></xct:EventToCommandBehavior>
        </ImageButton.Behaviors>
    </ImageButton>
    <Frame CornerRadius="20" HorizontalOptions="Start" WidthRequest="30" VerticalOptions="Center" Margin="0" Padding="10" BackgroundColor="{Binding IconBackgroundColor}">
        <Label Text="{Binding CurrentPage}" TextColor="White" HorizontalOptions="Center" VerticalOptions="Center" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" FontAttributes="Bold" />
    </Frame>
    <Label Text="{Binding PageCount,StringFormat='Of {0}'}" VerticalOptions="Center" FontAttributes="Bold"></Label>
    <ImageButton HeightRequest="40" BackgroundColor="Transparent" IsEnabled="{Binding AllowNextPageNavigation}">
        <ImageButton.Source>
            <FontImageSource FontFamily="FontAwesomeSolid"
                  Color="{Binding NextPageButtonForegroundColor}"
                 Glyph="&#xf105;"/>
        </ImageButton.Source>
        <ImageButton.Behaviors>
            <xct:EventToCommandBehavior EventName="Clicked" Command="{Binding MoveToNextPageCommand}"></xct:EventToCommandBehavior>
        </ImageButton.Behaviors>
    </ImageButton>
    <ImageButton HeightRequest="40" BackgroundColor="Transparent" IsEnabled="{Binding AllowLastPageNavigation}">
        <ImageButton.Source>
            <FontImageSource FontFamily="FontAwesomeSolid"
                   Color="{Binding LastPageButtonForegroundColor}"
                 Glyph="&#xf101;"/>
        </ImageButton.Source>
        <ImageButton.Behaviors>
            <xct:EventToCommandBehavior EventName="Clicked" Command="{Binding MoveToLastPageCommand}"></xct:EventToCommandBehavior>
        </ImageButton.Behaviors>
    </ImageButton>
</StackLayout>
Enter fullscreen mode Exit fullscreen mode

Here, we are placing buttons and labels horizontally in StackLayout and binding events and properties to it.

Reusing the build ContentView

  1. Add xmlns in xaml and give it a name.

    xmlns:customViews="clr-namespace:Xam.Views.Pagination" 
    x:Name="Page"
    
  2. Use the component wherever required, in the page

    <customViews:PaginationView 
                        CurrentPage="{Binding Source={x:Reference Page}, Path=BindingContext.PageNumberBackingField}"
                        PageCount="{Binding Source={x:Reference Page}, Path=BindingContext.PageCountBackingField}" 
                        OnPaginated="{Binding Source={x:Reference Page}, Path=BindingContext.OnPaginatedCommand}"
                        IconBackgroundColor="Red"
                        DisabledColor="Gray"/>
    

OnPaginatedCommand should be of type IAsyncComand where parameter int gives us the page number we are in.

Working GIF

Resources

You can find a link to Github repo with the code for this post

Top comments (2)

Collapse
praweshkafle profile image
Praweshkafle

Thank you sir for sharing this amazing pagination 😲 . Big fan sir .

Collapse
kingleo10 profile image
Niroj Dahal Author

Glad you liked it

Hacktoberfest is happening now!


It is a month-long celebration of open source. For a lot of devs, its their introduction to open source.



Check out the Hacktoberfest tag on DEV to keep up with the latest!