Trabalhar com listas no Xamarin Forms nem sempre é uma tarefa simples. Precisamos pensar na exibição de dados, consumos de internet e e tudo mais. Neste post vamos ver uma estratégia para atualizar o conteúdo da ListView sob demanda, conforme o usuário estiver vendo, o Infinite Scroll, utilizando o Prism, sem necessidade de plugin, sem necessidade de criar um novo Custom Control o Custom Renderer.

Criando o evento na ListView

Se você não conhece o Prism, eu já falei dele anteriormente neste post.

Vou partir do princípio que você já possui o Prism instalado no projeto.

O primeiro passo é construir a lista no XAML:

<ListView HasUnevenRows="true"
            ItemsSource="{Binding Produtos}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <StackLayout>
                    <Label Text="{Binding Nome}">
                </StackLayout>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Adicionando o Prism EventToCommandBehaviors

Até aqui não há nada de novo do que já estamos acostumados a fazer com ListViews no Xamarin.

Para conseguir fazer o infinite scroll de modo desacoplado e testável, a grande sacada do Prism é permitir que nós consigamos usar os eventos na View Model e não no code behind.

Para isto, o primeiro passo é adicionar o namespace dos Behaviors do Prism na sua Página:

xmlns:behaviors="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"

Depois de adicionar os Behaviors do Prism, podemos utilizar o EventToCommandBehavior. A classe EventToCommandBehavior fornece uma maneira conveniente de, em XAML, “ligar” eventos ao ICommand da View Model utilizando o padrão MVVM para evitar o que seja necessário escrever códigos no Code Behind.

<ListView.Behaviors>
    <behaviors:EventToCommandBehavior Command="{Binding ItemAppearingCommand}";
                                        EventArgsParameterPath="Item"
                                        EventName="ItemAppearing"/>
</ListView.Behaviors>

No código acima nós adicionamos a propriedade Behaviors da ListView, nativo do Xamarin, e dentro dela, adicionamos os Behaviors do Prism. Dos Behaviors do Prism selecionamos o EventToCommandBehavior, quer fará toda a mágica acontecer. Ele precisa de ao menos 3 parâmetros para ser executado: Command (que será o Command executado na View Model quando o evento acontecer), EventArgsParameterPath (que será os argumentos enviados ao Command) e EventName (que é o nome do evento que queremos escutar).

Em EventArgsParameterPath estamos passando o texto Item. Isto é suficiente para o Prism entender que estamos querendo obter o item atual que está sendo exibido neste momento na tela. Ele se encarregará de obte-lo da tela e envia-lo para o Command.

Veja como ficou a página inteira após adicionar os códigos acima:

<ContentPage x:Class=&amp;quot;MeuApp.Views.ProdutosPage>
             xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:behaviors="clr-namespace:Prism.Behaviors;assembly=Prism.Forms">
   <ContentPage.Content>
        <StackLayout>
            <ListView HasUnevenRows="true"
                      ItemsSource="{Binding Produtos}">
                <ListView.Behaviors>
                   <behaviors:EventToCommandBehavior Command="{Binding ItemAppearingCommand}"
                                                      EventArgsParameterPath="Item"
                                                      EventName="ItemAppearing" />
                </ListView.Behaviors>
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <StackLayout>
                               <Label Text="{Binding Nome}"/>
                            </StackLayout>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
           </ListView>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Obtendo o último elemento da lista e adicionando mais items

Pronto, agora tudo o que precisamos fazer é adicionar um Command na nossa ViewModel que receba o item como parâmetro e adicione mais items a lista Produtos:

public ICommand ItemAppearingCommand { get; set; }

E então instancia-lo no construtor da ViewModel:

ItemAppearingCommand = new Command((item) => 
{ 
    if(Produtos.Last().Id == ((Produto)item).Id)
    { 
        AtualizarListaDeProdutos();
    }
});

No Command acima estamos verificando se o último item da nossa lista de Produtos é o mesmo item que foi enviado pelo EventToCommandBehavior da view para view model. Perceba que pedimos o parâmetro item na instanciação do Command. Ele receberá o valor do Item que pedimos na View na propriedade EventArgsParameterPath.

Veja a classe completa:

using MvvmHelpers;
using Prism.Mvvm;
using Prism.Navigation;
using Prism.Services;
using MeuApp.Models;
using MeuApp.Services;
using System.Windows.Input;
using Xamarin.Forms;

namespace MeuApp.ViewModels
{
    public class ProdutosPageViewModel : BindableBase, INavigatingAware
    {
        public readonly IProdutoService _produtoService;

        public ObservableRangeCollection<Produto> Produtos { get; set; }

        public ICommand ItemAppearingCommand { get; set; }

        public ProdutosPageViewModel(IProdutoService produtoService)
        {
            _produtoService = produtoService;

            Produtos = new ObservableRangeCollection<Produto>();

            ItemAppearingCommand = new Command((item) =>
            { 
                if(Produtos[Produtos.Count -1].Id == ((Produto)item).Id)
                { 
                    AtualizarListaDeProdutos();
                }
            });
        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {
        }

        public void OnNavigatingTo(INavigationParameters parameters)
        {
            AtualizarListaDeProdutos();
        }

        private void AtualizarListaDeProdutos() => Produtos.AddRange(_produtoService.ObterProdutos());
    }
}

Feedback para o usuário

Pronto! Se você quiser, pode também adicionar um loading no rodapé da ListView para indicar ao usuário que está buscando mais items:

<ListView.Footer>
    <StackLayout IsVisible="{Binding BuscandoMaisProdutos}" Margin="0, 10, 0, 10">
       <ActivityIndicator IsRunning="true"
                            WidthRequest="40"
                            HeightRequest="40"/>
    </StackLayout>
</ListView.Footer>

E então setar a flag BuscandoMaisProdutos como true antes de chamarmos o serviço que irá buscar os produtos e como false quando os produtos retornarem:

if(Produtos[Produtos.Count -1].ProdutoId == ((Produto)item).ProdutoId)
{ 
    Device.BeginInvokeOnMainThread(()=> BuscandoMaisProdutos = true);
    AtualizarListaDeProdutos();
    Device.BeginInvokeOnMainThread(() => BuscandoMaisProdutos = false);
}

Estamos utilizando o Device.BeginInvokeOnMainThread para garantir que a atualização da propriedade e a notificação da alteração ocorreram na Main Thread e a view será notificada desta atualização.

Conclusão

O EventToCommandBehavior pode ser usado para tratar diversos eventos na View Model, isto facilita bastante a testabilidade do sistema, além de reduzir a complexidade.

Você pode ler mais sobre o EventToCommandBehavior aqui.

Imagem utilizada no post PixaBay

Modificado pela ultima vez: 15 de maio de 2020

Comentários

Escreva uma resposta ou comentário

Seu endereço de e-mail não será publicado.

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.