Infinite Scroll and Loading Indicator in ListView in MAUI MVVM .NET 9 [GamesCatalog] - Part 5

Previous part: Consuming an API and Populating a List with the Results in MAUI MVVM .NET 9 [GamesCatalog] - Part 4

Step 1. Let's create a ViewModelBase that will contain the parameters to be used by the other pages in the system.

 ViewModelBase

Step 2. Let's define two variables: one to indicate that the system is performing an action and another to check the internet connection.

 public partial class ViewModelBase : ObservableObject
 {
     bool isBusy;

     public bool IsBusy
     {
         get => isBusy; set
         {
             if (isBusy != value)
             {
                 SetProperty(ref (isBusy), value);
             }
         }
     }

     protected static bool IsOn => Connectivity.NetworkAccess == NetworkAccess.Internet;
 }

Step 3. Let's make IGDBResultsVM inherit from the created base.

public partial class IGDBResultsVM : ViewModelBase

Step 4. In the search results function, we will use the IsBusy variable to define when this function is being executed.

IsBusy variable

Step 5. In IGDBResults.xaml, we will define the ActivityIndicator based on the variable.

IGDBResults

Step 5.1. Code.

<ListView.Footer>
    <ActivityIndicator
        HorizontalOptions="Center"
        IsRunning="{Binding IsBusy}"
        IsVisible="{Binding IsBusy}"
        VerticalOptions="Center"
        Color="{StaticResource ActiveColor}" />
</ListView.Footer>

Step 5.2. Now we have a loading indicator at the end of the list.

Loading indicator

Step 6. In IGDBResultsVM, define the variable for the current page.

public int CurrentPage { get; set; }

Step 7. Define the use of the variable in the list load during the search.

Variable

Step 8. Let's create a function to load additional pages in the list.

[RelayCommand]
public Task LoadMore()
{
    CurrentPage++;
    return LoadIGDBGamesList(CurrentPage);
}

Step 9. Let's create the Behavior class that will handle loading additional pages in the ListView.

ListView

Step 9.1. Code.

 public partial class InfiniteScrollBehavior : Behavior<ListView>
   {
       public static readonly BindableProperty LoadMoreCommandProperty =
                              BindableProperty.Create(nameof(LoadMoreCommand), typeof(ICommand), typeof(InfiniteScrollBehavior), null);

       public ICommand LoadMoreCommand
       {
           get
           {
               return (ICommand)GetValue(LoadMoreCommandProperty);
           }
           set
           {
               SetValue(LoadMoreCommandProperty, value);
           }
       }

       public ListView AssociatedObject
       {
           get;
           private set;
       }

       protected override void OnAttachedTo(ListView bindable)
       {
           base.OnAttachedTo(bindable);
           AssociatedObject = bindable;
           bindable.BindingContextChanged += Bindable_BindingContextChanged;

           bindable.ItemAppearing += InfiniteListView_ItemAppearing;
       }

       private void Bindable_BindingContextChanged(object sender, EventArgs e)
       {
           OnBindingContextChanged();
       }

       protected override void OnBindingContextChanged()
       {
           base.OnBindingContextChanged();
           BindingContext = AssociatedObject.BindingContext;
       }

       protected override void OnDetachingFrom(ListView bindable)
       {
           base.OnDetachingFrom(bindable);
           bindable.BindingContextChanged -= Bindable_BindingContextChanged;
           bindable.ItemAppearing -= InfiniteListView_ItemAppearing;
       }

       void InfiniteListView_ItemAppearing(object sender, ItemVisibilityEventArgs e)
       {
           IList items = AssociatedObject.ItemsSource as IList;

           if (items != null)
           {
               if (items.Count > 0)
               {
                   int index = items.Count - 1;
                   if (e.Item == items[index])
                   {
                       if (LoadMoreCommand != null && LoadMoreCommand.CanExecute(null)) LoadMoreCommand.Execute(null);
                   }
               }
           }
       }
   }

Step 10. In IGDBResults, let's remove the ScrollView that was used to display the fixed list and add the behavior to the list.

ScrollView

Content page

Step 10.1. Add the link to the behaviors.

  xmlns:behaviors="clr-namespace:GamesCatalog.Utils.Behaviors"

Step 10.2. Code for the border containing the list.

<Border
    Grid.Row="1"
    Margin="5"
    Padding="5"
    StrokeShape="RoundRectangle 10">
    <ListView
        CachingStrategy="RecycleElement"
        HasUnevenRows="True"
        ItemsSource="{Binding ListGames}"
        SelectionMode="None">
        <ListView.Behaviors>
            <behaviors:InfiniteScrollBehavior LoadMoreCommand="{Binding LoadMoreCommand}" />
        </ListView.Behaviors>
        <ListView.ItemTemplate>
            <DataTemplate x:DataType="model:UIIGDBGame">
                <ViewCell>
                    <Border
                        Margin="0,0,0,5"
                        Padding="10"
                        BackgroundColor="#101923"
                        Stroke="#2B659B"
                        StrokeShape="RoundRectangle 10">
                        <Grid Padding="10">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>
                            <Image
                                Grid.Column="0"
                                Aspect="AspectFit"
                                HeightRequest="100"
                                Source="{Binding CoverUrl}"
                                VerticalOptions="Center"
                                WidthRequest="150" />
                            <StackLayout Grid.Column="1" Margin="10,0,0,0">
                                <Label
                                    FontSize="Large"
                                    LineBreakMode="TailTruncation"
                                    Text="{Binding Name}" />
                                <Label
                                    FontAttributes="Italic"
                                    FontSize="Micro"
                                    Text="{Binding ReleaseDate, StringFormat='Release: {0:F0}'}"
                                    TextColor="#98BDD3" />
                                <Label
                                    FontSize="Micro"
                                    Text="{Binding Platforms, StringFormat='Platforms: {0:F0}'}"
                                    TextColor="#98BDD3" />
                            </StackLayout>
                        </Grid>
                    </Border>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
        <ListView.Footer>
            <ActivityIndicator
                HorizontalOptions="Center"
                IsRunning="{Binding IsBusy}"
                IsVisible="{Binding IsBusy}"
                VerticalOptions="Center"
                Color="{StaticResource ActiveColor}" />
        </ListView.Footer>
    </ListView>
</Border>

Step 11. This way, we load information based on the user's scroll.

Load information

Next part: Passing an Object from a List to Another Page in MAUI MVVM .NET 9 [GamesCatalog] - Part 6

Code on git: GamesCatalog git

Up Next
    Ebook Download
    View all
    Learn
    View all