.NET MAUI - Usando o banco de dados SQLite - II


  Neste artigo vamos criar uma aplicação .NET MAUI para gerenciar um pequena biblioteca de livros pessoal realizando a persistência dos dados no SQLite.

Continuando a primeira parte do artigo vamos criar as views , fazer as vinculações e registro dos serviços e preparar nossa aplicação para ser executada.

Criando as Views

Vamos agora criar na pasta MVVM/Views as respectivas views definindo o leiaute da interface com o usuário e vinculando cada view à sua view model. Todas as views são do tipo Content Page.

1- LivrosPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
    x:Class="MeusLivros.MVVM.Views.LivrosPage"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:model="clr-namespace:MeusLivros.MVVM.Models"
    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    xmlns:viewmodel="clr-namespace:MeusLivros.MVVM.ViewModels"
    Title="Meus Livros"
    x:DataType="viewmodel:LivrosViewModel">
    <ContentPage.Resources>
        <toolkit:BoolToObjectConverter
            x:Key="BoolToObjectConverter"
            FalseObject="Leitura ❌"
            TrueObject="Concluída ✔" />
    </ContentPage.Resources>
    <ContentPage.Behaviors>
        <toolkit:EventToCommandBehavior Command="{Binding GetLivrosCommand}" EventName="Appearing" />
    </ContentPage.Behaviors>
    <Grid
        Margin="20"
        RowDefinitions="Auto,*"
        RowSpacing="10">
        <Grid Grid.Row="0" ColumnDefinitions="*,*">
            <SearchBar Grid.Column="0"
                 Placeholder="Localizar Livro"
                 TextChanged="SearchBar_TextChanged"/>
            <Button
                Grid.Column="1"
                Background="gold"
                Command="{Binding AddLivroCommand}"
                FontAttributes="Bold"
                HorizontalOptions="End"
                Text="Novo Livro"
                TextColor="Black" />
        </Grid>
              <CollectionView x:Name="ColView1" Grid.Row="1" ItemsSource="{Binding MeusLivros}">
            <CollectionView.ItemsLayout>
                <LinearItemsLayout ItemSpacing="4" Orientation="Vertical" />
            </CollectionView.ItemsLayout>
            <CollectionView.ItemTemplate>
                <DataTemplate x:DataType="model:Livro">
                    <Border
                        Padding="15"
                        Background="lightgray"
                        Stroke="Dimgray"
                        StrokeThickness="2">
                        <Border.StrokeShape>
                            <RoundRectangle CornerRadius="10,10,10,10" />
                        </Border.StrokeShape>
                        <Grid RowDefinitions="Auto,*" RowSpacing="4">
                            <Image
                                Grid.Row="0"
                                Aspect="AspectFill"
                                HeightRequest="200"
                                HorizontalOptions="Center"
                                Source="{Binding ImagemUrl}"
                                VerticalOptions="Center" />
                            <Grid Grid.Row="1" RowDefinitions="Auto,Auto,Auto,Auto">
                                <Label
                                    Grid.Row="0"
                                    FontAttributes="Bold"
                                    FontSize="Medium"
                                    Text="{Binding Titulo}" />
                                <Label
                                    Grid.Row="1"
                                    Margin="0,2"
                                    FontSize="Small"
                                    Text="{Binding Autor}" />
                                <Label
                                    Grid.Row="2"
                                    Margin="0,2"
                                    FontSize="Small"
                                    Text="{Binding Paginas,  StringFormat='{0:F0} páginas'}" />
                                <Grid
                                    Grid.Row="3"
                                    Margin="0,5"
                                    ColumnDefinitions="Auto,Auto,*">
                                    <Button
                                        Grid.Column="0"
                                        BackgroundColor="#4A88DA"
                                        Command="{Binding Source={RelativeSource 
                                         AncestorType={x:Type viewmodel:LivrosViewModel}}, Path=UpdateLivroCommand}"
                                        CommandParameter="{Binding .}"
                                        FontAttributes="Bold"
                                        Text="Atualizar"
                                        TextColor="#eff5f3" />
                                    <Button
                                        Grid.Column="1"
                                        Margin="8,0"
                                        BackgroundColor="#F44336"
                                        Command="{Binding Source={RelativeSource AncestorType={x:Type 
                                        viewmodel:LivrosViewModel}}, Path=DeleteLivroCommand}"
                                        CommandParameter="{Binding .}"
                                        FontAttributes="Bold"
                                        Text="Deletar"
                                        TextColor="#eff5f3" />
                                    <Label
                                        Grid.Column="2"
                                        HorizontalOptions="End"
                                        LineBreakMode="TailTruncation"
                                        Text="{Binding LeituraConcluida, Converter={StaticResource BoolToObjectConverter}}"
                                        TextColor="Blue"
                                        VerticalOptions="Center" />
                                </Grid>
                            </Grid>
                        </Grid>
                    </Border>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </Grid>
</ContentPage>

Destaques do código :

  • x:DataType:
  • ContentPage.Resources:
  • ContentPage.Behaviors:

    Neste código definidos nos botões de comando para Atualizar e Deletar :
     

  • Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:LivrosViewModel}}, Path=UpdateLivroCommand}": Este atributo associa um comando ao botão sendo que o comando é definido no ViewModel e é acionado quando o botão é pressionado:
  • 2- AddLivroPage.xaml

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage
        x:Class="MeusLivros.MVVM.Views.AddLivroPage"
        xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:viewmodel="clr-namespace:MeusLivros.MVVM.ViewModels"
        Title="Incluir Livro">
        <VerticalStackLayout Margin="20">
            <Entry Placeholder="Titulo" Text="{Binding LivroTitulo}" />
            <Entry Placeholder="Autor" Text="{Binding LivroAutor}" />
            <Entry Placeholder="Imagem" Text="{Binding LivroImagemUrl}" />
            <Entry Placeholder="No. de Páginas" Text="{Binding LivroPaginas}" />
            <Entry Placeholder="Emprestado para..." Text="{Binding LivroEmprestadoPara}" />
            <HorizontalStackLayout>
                <CheckBox IsChecked="{Binding LivroLeituraConcluida}" />
                <Label Text="Você concluiu a leitura deste livro ?" VerticalOptions="Center" />
            </HorizontalStackLayout>
            <Button
                Background="gold"
                Command="{Binding AddLivroCommand}"
                FontAttributes="Bold"
                Text="Novo Livro"
                TextColor="Black" />
        </VerticalStackLayout>
    </ContentPage>

    Vamos entender o código:

    xmlns:viewmodel: Este atributo xmlns define um namespace para o namespace de ViewModels (MeusLivros.MVVM.ViewModels). Isso permite que você associe elementos da interface do usuário a propriedades e comandos definidos nos ViewModels.

    A seguir temos as seguintes vinculações:
     

    3- UpdateLivroPage.xaml

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage
        x:Class="MeusLivros.MVVM.Views.UpdateLivroPage"
        xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:viewmodel="clr-namespace:MeusLivros.MVVM.ViewModels"
        Title="Atualizar Livro"
        x:DataType="viewmodel:UpdateLivroViewModel">
        <VerticalStackLayout Margin="20">
            <Entry Placeholder="Titulo" Text="{Binding Livro.Titulo}" />
            <Entry Placeholder="Autor" Text="{Binding Livro.Autor}" />
            <Entry Placeholder="Imagem" Text="{Binding Livro.ImagemUrl}" />
            <Entry Placeholder="No. de Páginas" Text="{Binding Livro.Paginas}" />
            <Entry Placeholder="Emprestado para..." Text="{Binding Livro.EmprestadoPara}" />
            <HorizontalStackLayout>
                <CheckBox IsChecked="{Binding Livro.LeituraConcluida}" />
                <Label Text="Já concluiu a leitura deste livro ?" VerticalOptions="Center" />
            </HorizontalStackLayout>
            <Button
                Background="#4A88DA"
                Command="{Binding UpdateLivroCommand}"
                Text="Atualizar Livro"
                TextColor="#eff5f3" />
        </VerticalStackLayout>
    </ContentPage>

    A lógica desta view é idêntica à anterior, a diferença é que aqui estamos vinculando a ação ao comando UpdateLivroCommand.

    A seguir temos o código que faz a vinculação de cada view com a respectiva view model no arquivo code-behind:

    1- LivrosPages.xaml.cs

    public partial class LivrosPage : ContentPage
    {
        private readonly ILivroService _livroService;
        public LivrosPage(LivrosViewModel livrosViewModel, 
                          ILivroService livroService)
        {
            InitializeComponent();
            BindingContext = livrosViewModel;
            _livroService = livroService;
        }
        private async void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
        {
            var livros = await _livroService.GetLivroTituloAsync(((SearchBar)sender).Text);
            ColView1.ItemsSource = livros;
        }
    }

    2- AddLivroPage.xaml.cs

    public partial class AddLivroPage : ContentPage
    {
        public AddLivroPage(AddLivroViewModel addLivroViewModel)
        {
    	InitializeComponent();
                 BindingContext = addLivroViewModel;
        }
    }

    3- UpdateLivroPage.xaml.cs

    public partial class UpdateLivroPage : ContentPage
    {
    	public UpdateLivroPage(UpdateLivroViewModel updateLivroViewModel)
    	{
    		InitializeComponent();
    		BindingContext = updateLivroViewModel;
    	}
    }

    Não podemos esquecer de registrar os serviços no contêiner DI na classe MauiProgram:

    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiApp<App>()
                .UseMauiCommunityToolkit()
                .ConfigureFonts(fonts =>
                {
                    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                    fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
                });
    #if DEBUG
    		builder.Logging.AddDebug();
    #endif
            // ViewModels
            builder.Services.AddSingleton<LivrosViewModel>();
            builder.Services.AddTransient<AddLivroViewModel>();
            builder.Services.AddTransient<UpdateLivroViewModel>();
            // Views
            builder.Services.AddSingleton<LivrosPage>();
            builder.Services.AddTransient<AddLivroPage>();
            builder.Services.AddTransient<UpdateLivroPage>();
            // Service
            builder.Services.AddSingleton<ILivroService, LivroService>();
            return builder.Build();
        }
    }

    Neste código destacamos o seguinte:

    .UseMauiCommunityToolkit(): Este método configura o aplicativo para usar o community toolkit maui(CommunityToolkit) que fornece funcionalidades e controles adicionais para a construção de aplicativos.
     

  • builder.Services.AddSingleton<LivrosViewModel>();: Aqui, o construtor está configurando a injeção de dependência para o ViewModel LivrosViewModel, registrando-o como um serviço Singleton. Isso significa que uma única instância do LivrosViewModel será compartilhada em todo o aplicativo.
     
  • builder.Services.AddTransient<AddLivroViewModel>(); e builder.Services.AddTransient<UpdateLivroViewModel>();: Da mesma forma, os ViewModels AddLivroViewModel e UpdateLivroViewModel são configurados como serviços transientes. Isso significa que uma nova instância desses ViewModels será criada sempre que for solicitada.
     
  • builder.Services.AddSingleton<LivrosPage>();, builder.Services.AddTransient<AddLivroPage>();, e builder.Services.AddTransient<UpdateLivroPage>();: Os tipos de página correspondentes aos ViewModels também são configurados como serviços. LivrosPage é registrado como Singleton, enquanto AddLivroPage e UpdateLivroPage são registrados como transientes.
     
  • builder.Services.AddSingleton<ILivroService, LivroService>();: Aqui, o serviço ILivroService é configurado para usar a implementação LivroService como implementação concreta. Isso permite que outros componentes do aplicativo dependam de ILivroService e usem a implementação LivroService para acessar funcionalidades relacionadas a livros.

    Tipos de vida usados nos serviços:
     

    1. LivrosPage e ILivroService como Singleton:
      • LivrosPage é a página principal do aplicativo que exibe uma lista de livros. Ela pode ser usada em toda a vida do aplicativo, e é comum manter as páginas principais como instâncias Singleton, pois elas geralmente não mudam ao longo da sessão do usuário.
      • ILivroService é o serviço responsável por interagir com a camada de dados, neste caso, ILivroService, é frequentemente registrado como Singleton para garantir que haja uma única instância compartilhada em todo o aplicativo. Isso é útil para manter um único estado de conexão com o banco de dados ou gerenciar recursos compartilhados, economizando recursos e garantindo consistência em toda a aplicação.
         
    2. AddLivroViewModel e UpdateLivroViewModel como Transient:
      • AddLivroViewModel e UpdateLivroViewModel são ViewModels usadas para controlar as operações de adição e atualização de livros. Essas ViewModels podem ser usadas temporariamente durante a adição ou atualização de um livro específico e não precisam ser compartilhados entre várias partes do aplicativo. Portanto, eles são registrados como Transient, o que significa que uma nova instância será criada sempre que necessário e será descartada quando não for mais usada. Isso evita o acúmulo de estado desnecessário.

    Agora vamos configurar as rotas usadas no projeto no arquivo AppShell.xaml e AppShell.xaml.cs .

    1- AppShell.xaml

    <?xml version="1.0" encoding="UTF-8" ?>
    <Shell
        x:Class="MeusLivros.AppShell"
        xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:local="clr-namespace:MeusLivros"
        xmlns:views="clr-namespace:MeusLivros.MVVM.Views"
        Shell.FlyoutBehavior="Disabled">
        <ShellContent ContentTemplate="{DataTemplate views:LivrosPage}" Route="LivrosPage" />
    </Shell>

    Este código define a estrutura básica do Shell do aplicativo .NET MAUI, desativando o menu de navegação lateral (flyout) e configurando LivrosPage como a página principal exibida quando o aplicativo é iniciado.

    <ShellContent>: Este é um elemento dentro do Shell que define o conteúdo principal da página. Ele está associado a um modelo de conteúdo (ContentTemplate) que especifica qual página será exibida como conteúdo principal. Neste caso, a página LivrosPage será exibida como conteúdo principal quando o aplicativo for iniciado. O atributo Route especifica o rótulo ou identificador da página.

    2- AppShell.xaml.cs

    public partial class AppShell : Shell
    {
        public AppShell()
        {
            InitializeComponent();
            RegisterForRoute<AddLivroPage>();
            RegisterForRoute<UpdateLivroPage>();
        }
        protected void RegisterForRoute<T>()
        {
            Routing.RegisterRoute(typeof(T).Name, typeof(T));
        }
    }

    Vamos entender o código usado:
     

    1. InitializeComponent(): Este método é gerado automaticamente e é usado para inicializar os componentes do layout XAML associados à classe AppShell. Essa chamada garante que o layout definido no arquivo XAML seja carregado e configurado corretamente.
       
    2. RegisterForRoute<AddLivroPage>(); e RegisterForRoute<UpdateLivroPage>();: Esses métodos são chamados no construtor para registrar páginas específicas que podem ser navegadas usando rotas no aplicativo. Eles usam o método Routing.RegisterRoute para associar o nome da classe da página (AddLivroPage e UpdateLivroPage) com a classe real que representa a página.
       
    3. protected void RegisterForRoute<T>(): Este é um método genérico que permite registrar páginas para navegação com base no tipo de página. Ele usa reflection para obter o nome da classe do tipo T e, em seguida, registra uma rota com esse nome.

    Com isso temos tudo pronto para por o nosso projeto para funcionar.

    Executando o projeto em um emulador Android teremos o seguinte resultado:

    1- A tela inicial

    2- Incluindo um novo livro

    3- Atualizando um livro

    4- Excluindo um livro

    Pegue o código do projeto aqui:  MeusLivrosApp.zip

    "['Senhor'] O teu reino é um reino eterno; o teu domínio dura em todas as gerações."
    Salmos 145:13

    Referências:


    José Carlos Macoratti