Creating a Tab bar menu in the shell but I have issues calling data-bound xaml pages.

Manie Verster

Member
Joined
Nov 22, 2023
Messages
22
Programming Experience
10+
I recently started learning mobile app development in .NET Maui. I began with data-bound XAML pages. Then I wanted to create a menu in my app. After exploring some possibilities I decided on tab bar menus but I have a problem calling data-bound XAML pages. Here are the data classes I am using. This might be overkill but I wanted to rather give too much information than to little. Please can anyone help!

The error I am getting:

System.InvalidOperationException
Unable to resolve service for type 'VehicleLogs.VehicleLogDatabase' while attempting to activate 'VehicleLogs.Vehicles'.

Create the SQLite database.

Create the database:
using SQLite;

namespace VehicleLogs
{
    public class VehicleLogDatabase
    {
        private const string db_name = "VehicleLogBook.db3";

        private readonly SQLiteAsyncConnection _connection;

        public VehicleLogDatabase()
        {
            _connection = new SQLiteAsyncConnection(Path.Combine(FileSystem.AppDataDirectory, db_name));
            _connection.CreateTableAsync<VehicleMaster>();
            _connection.CreateTableAsync<VehicleLog>();
        }

        public async Task<List<VehicleMaster>> GetVehicles()
        {
            return await _connection.Table<VehicleMaster>().ToListAsync();
        }

        public async Task<VehicleMaster> GetVehiclesById(int id)
        {
            return await _connection.Table<VehicleMaster>().Where(x => x.VehicleID == id).FirstOrDefaultAsync();
        }

        public async Task Create(VehicleMaster vehicleMaster)
        {
            await _connection.InsertAsync(vehicleMaster);
        }

        public async Task Update(VehicleMaster vehicleMaster)
        {
            await _connection.UpdateAsync(vehicleMaster);
        }

        public async Task Delete(VehicleMaster vehicleMaster)
        {
            await _connection.DeleteAsync(vehicleMaster);
        }

        public async Task<List<VehicleLog>> GetLogbook()
        {
            return await _connection.Table<VehicleLog>().ToListAsync();
        }

        public async Task<VehicleLog> GetLogbookById(int id)
        {
            return await _connection.Table<VehicleLog>().Where(x => x.LogID == id).FirstOrDefaultAsync();
        }

        public async Task Create(VehicleLog vehicleLogbook)
        {
            await _connection.InsertAsync(vehicleLogbook);
        }

        public async Task Update(VehicleLog vehicleLogbook)
        {
            await _connection.UpdateAsync(vehicleLogbook);
        }

        public async Task Delete(VehicleLog vehicleLogbook)
        {
            await _connection.DeleteAsync(vehicleLogbook);
        }
    }
}

The Vehicle Master table.
Vehicle Master table:
using SQLite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace VehicleLogs
{
    [Table("VehicleMaster")]
    public class VehicleMaster
    {
        [PrimaryKey]
        [AutoIncrement]
        [Column("VehicleID")]
        public int VehicleID { get; set; }
        [Column("RegNr")]
        public string? RegNr { get; set; }
        [Column("LastOdometer")]
        public string? LastOdometer { get; set; }
    }
}

The Vehicle Log table.
Vehicle Log table:
using SQLite;

namespace VehicleLogs
{
    [Table("VehicleLog")]
    public class VehicleLog
    {
        [PrimaryKey]
        [AutoIncrement]
        [Column("LogID")]
        public int LogID { get; set; }
        [PrimaryKey]
        [Column("RegNr")]
        public string? RegNr { get; set; }
        [Column("StartOdometer")]
        public string? StartOdometer { get; set; }
        [Column("EndOdometer")]
        public string? EndOdometer { get; set; }
    }
}

Vehicle Master XAML. From here I will only show the Vehicle Master files
Vehicle Master XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="VehicleLogs.Vehicles"
             Title="Vehicles">
    <FlexLayout Direction="Column">
        <VerticalStackLayout Padding="10"  FlexLayout.Basis="400" BackgroundColor="Beige">
            <Entry x:Name="regNrEntryField" Placeholder="Reg Number" />
            <Entry x:Name="lastOdometerEntryField" Placeholder="Last Odometer" />
            <Button x:Name="saveButton" Text="Save" Clicked="SaveButtonClicked"/>
        </VerticalStackLayout>
        <ListView x:Name="ListView" HasUnevenRows="True" ItemTapped="ListView_ItemTapped">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <VerticalStackLayout Padding="5">
                            <Label Text="(Binding RegNr)" FontSize="17" FontAttributes="Bold" />
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                    <ColumnDefinition Width="Auto" />
                                </Grid.ColumnDefinitions>
                                <Label Grid.Column="0" Text="={Binding RegNr}" FontAttributes="Bold" />
                                <Label Grid.Column="1" Text="={Binding LastOdometer}" FontAttributes="Bold" />
                            </Grid>
                        </VerticalStackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </FlexLayout>
</ContentPage>

Vehicle Master XAML cs file
Vehicle Master XAML cs file:
namespace VehicleLogs;

public partial class Vehicles : ContentPage
{

    private readonly VehicleLogDatabase _dbService;
    private int _editVehicleID;

    public Vehicles(VehicleLogDatabase dbService)
    {
        InitializeComponent();
        _dbService = dbService;
        Task.Run(async () => ListView.ItemsSource = await _dbService.GetVehicles());
    }

    private async void SaveButtonClicked(object sender, EventArgs e)
    {
        if (_editVehicleID == 0)
        {
            await _dbService.Create(new VehicleMaster
            {
                RegNr = regNrEntryField.Text,
                LastOdometer = lastOdometerEntryField.Text
            });
        }
        else
        {
            await _dbService.Update(new VehicleMaster
            {
                VehicleID = _editVehicleID,
                RegNr = regNrEntryField.Text,
                LastOdometer = lastOdometerEntryField.Text
            });
            _editVehicleID = 0;
        }
        regNrEntryField.Text = string.Empty;
        lastOdometerEntryField.Text = string.Empty;
        ListView.ItemsSource = await _dbService.GetVehicles();
    }

    private async void ListView_ItemTapped(object sender, ItemTappedEventArgs e)
    {
        var vehicleMaster = (VehicleMaster)e.Item;
        var action = await DisplayActionSheet("Action", "Cancel", "Edit", "Delete");

        switch (action)
        {
            case "Edit":
                _editVehicleID = vehicleMaster.VehicleID;
                regNrEntryField.Text = vehicleMaster.RegNr;
                lastOdometerEntryField.Text = vehicleMaster.LastOdometer;

                break;
            case "Delete":
                await _dbService.Delete(vehicleMaster);
                ListView.ItemsSource = await _dbService.GetVehicles();
                break;
        }
    }
}

AppShell XAML with tab bar menu
AppShell XAML:
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="VehicleLogs.AppShell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:VehicleLogs"
    Shell.FlyoutBehavior="Disabled"
    Title="VehicleLogs">

    <TabBar>
        <Tab Title="Home">
            <ShellContent
                Title="Home"
                ContentTemplate="{DataTemplate local:MainPage}"
                Route="MainPage"/>
        </Tab>
        <Tab Title="Vehicles">
            <ShellContent
                Title="Vehicles"
                ContentTemplate="{DataTemplate local:Vehicle}"
                Route="Vehicle"/>
        </Tab>
        <Tab Title="Log Mileage">
            <ShellContent
                Title="Log Mileage"
                ContentTemplate="{DataTemplate local:LogVehicle}"
                Route="LogVehicle"/>
        </Tab>
    </TabBar>

</Shell>
 
How are you registering VehicleLogDatabase into the inversion of control container?
 
Solution
Hi SktDiver,

Thanks for the speedy reply. Your question reminded me of something I forgot to add in the MauiProgram.cs. See below.

I forgot to add builder.Services.AddSingleton<VehicleLogDatabase>();

Here is the full code:

public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>().ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
}).UseMauiCommunityToolkit();
builder.Services.AddSingleton<VehicleLogDatabase>();
builder.Services.AddTransient<MainPage>();
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}

Thank you for this and I will test and let you know how it went.
 
Hi SktDiver,

Thanks for the speedy reply. Your question reminded me of something I forgot to add in the MauiProgram.cs. See below.

I forgot to add builder.Services.AddSingleton<VehicleLogDatabase>();

Here is the full code:

public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>().ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
}).UseMauiCommunityToolkit();
builder.Services.AddSingleton<VehicleLogDatabase>();
builder.Services.AddTransient<MainPage>();
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}

Thank you for this and I will test and let you know how it went.

Hi SkyDiver,

Thank you, the above was definitely the solution. Just by you asking the question I was able to solve my problem.
 
Back
Top Bottom