Xamarin Forms- Let's Build Web-like Pagination
Niroj Dahal
Posted on July 28, 2021
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
- Right Click on Shared Project and Add ContentView (xaml) and name it to PaginationView.
- 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")]
- 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);
}```
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;
}
}
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;
}
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=""/>
</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=""/>
</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=""/>
</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=""/>
</ImageButton.Source>
<ImageButton.Behaviors>
<xct:EventToCommandBehavior EventName="Clicked" Command="{Binding MoveToLastPageCommand}"></xct:EventToCommandBehavior>
</ImageButton.Behaviors>
</ImageButton>
</StackLayout>
Here, we are placing buttons and labels horizontally in StackLayout and binding events and properties to it.
Reusing the build ContentView
-
Add xmlns in xaml and give it a name.
xmlns:customViews="clr-namespace:Xam.Views.Pagination" x:Name="Page"
-
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.
Resources
You can find a link to Github repo with the code for this post
Posted on July 28, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.