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.