Home > database >  Injecting sqlite database into MAUI ViewModels gives error that ViewModel does not define parameterl
Injecting sqlite database into MAUI ViewModels gives error that ViewModel does not define parameterl

Time:11-25

I am new to MAUI and I have a working project that uses an sqlite database to store my data for my project. I am trying to inject my database access object into the ViewModel for one of my Content Pages. I had it working previously by just creating ("new'ing up") my database access object and the database and project worked fine. When I changed this so that I would inject my database access object into the ViewModel's constructor I get an error:

/Users/RemoteCommand/Projects/Notes/Views/AllNotesPage.xaml(9,9): Error: XLS0507: Type 'AllNotes' is not usable as an object element because it is not public or does not define a public parameterless constructor or a type converter. (Notes) IntelliSense

Here is my XAML file:

<?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"
              xmlns:models="clr-namespace:Notes.Models"
             x:Class="Notes.Views.AllNotesPage"
             Title="AllNotesPage">

    <ContentPage.BindingContext>
        <models:AllNotes />
    </ContentPage.BindingContext>

    <ContentPage.ToolbarItems>
        <!--<ToolbarItem Text="Add" Clicked="Add_Clicked" IconImageSource="{FontImage Glyph=' ', Color=White, Size=22}"/>-->
        <ToolbarItem Text="Add" Command="{Binding AddClickedCommand}" IconImageSource="{FontImage Glyph=' ', Color=White, Size=22}"/>
    </ContentPage.ToolbarItems>

    <CollectionView x:Name="notesCollection"
                    ItemsSource="{Binding Notes}"
                    Margin="20"
                    SelectionMode="Single"
                    SelectionChanged="notesCollection_SelectionChanged">

        <CollectionView.ItemsLayout>
            <LinearItemsLayout Orientation="Vertical" ItemSpacing="10"/>
        </CollectionView.ItemsLayout>

        <CollectionView.ItemTemplate>
            <DataTemplate>
                <StackLayout>
                    <Label Text="{Binding Text}" FontSize = "22"/>
                    <Label Text="{Binding Date}" FontSize="14" TextColor="Silver"/>
                </StackLayout>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
</ContentPage>

Here is my code behind:

namespace Notes.Views;
using Notes.Models;
using Notes.Views;
using Notes.Data;
using CommunityToolkit.Mvvm.Input;

public partial class AllNotesPage : ContentPage
{   
    public AllNotesPage()
    {
        InitializeComponent();
    }
    async void notesCollection_SelectionChanged(System.Object sender, Microsoft.Maui.Controls.SelectionChangedEventArgs e)
    {
        if(e.CurrentSelection.Count != 0)
        {
            var note = (Note)e.CurrentSelection[0];
            await Shell.Current.GoToAsync($"{nameof(NotePage)}?{nameof(NotePage.ItemId)}={note.ID}");
        }
    }
}

Here is my ViewModel:

using System;
using System.Collections.ObjectModel;
using Notes.Data;
using Notes.Views;
using CommunityToolkit.Mvvm;
using CommunityToolkit.Mvvm.Input;

namespace Notes.Models;

public partial class AllNotes
{
    NotesDatabase _notesDatabase;

    public ObservableCollection<Note> Notes { get; set; } = new ObservableCollection<Note>();

    public AllNotes(NotesDatabase notesDatabase)
    {
        _notesDatabase = notesDatabase;
        LoadNotes();
    }
    [RelayCommand]
    async void AddClicked()
    {
        await Shell.Current.GoToAsync(nameof(NotePage));
    }
    public async void LoadNotes()
    {
        Notes.Clear();
        List<Note> notes = await _notesDatabase.GetItemsAsync();
        foreach(Note note in notes)
        {
            Notes.Add(note);
        }
    }
}

and here is my MauiProgram where I define the dependency injection:

using Microsoft.Extensions.Logging;
using Notes.Views;
using Notes.Models;
using Notes.Data;

namespace Notes;

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");
            });

#if DEBUG
        builder.Logging.AddDebug();
#endif

        builder.Services.AddSingleton<AllNotes>();
        builder.Services.AddSingleton<AllNotesPage>();
        builder.Services.AddSingleton<NotesDatabase>();
        
        return builder.Build();
    }
}

[Note: I have switched the AddSingleton to Add Transient for this page for these definitions to see if that would fix the problem but it did not]

I've tried a really basic dependency injection on an earlier test project where I injected my data access object into the code behind and I got the same error about missing a parameterless constructor and it turns out what I was missing was defining my data access object AND ContentPage as a Transient or Singleton in MauiProgram (Which is why for this project I added the data access object, ContentPage, and ViewModel as Singletons to the MauiProgram). Once I did that it worked, but now that I am using a ViewModel that I bind in the XAML I can't seem to get DI working for the ViewModel.

Please help this greenhorn!

Sincerely,

CodePudding user response:

Doing this in XAML is an open issue: Resolve XAML BindingContext from ServiceCollection.

For now, do this via code behind's constructor, with a parameter:

public AllNotesPage(AllNotes vm)
{
    InitializeComponent();
    BindingContext = vm;
}

DI will inject vm, doing the needed instantiation of AllNotes and its NotesDatabase.

  • Related