C
C#3mo ago
DaClownie

✅ How to access specific fields from an [ObservableProperty] ObservableCollection<Object>?

Hoping to pick someone's brain on MVVM and MAUI. So I have a ViewModel that looks like:
public partial class TermsViewModel : ObservableObject
{
public TermsViewModel()
{
Terms = new ObservableCollection<Term>();
}

[ObservableProperty]
ObservableCollection<Term> terms;
}
public partial class TermsViewModel : ObservableObject
{
public TermsViewModel()
{
Terms = new ObservableCollection<Term>();
}

[ObservableProperty]
ObservableCollection<Term> terms;
}
and I want to have the xaml create each of the objects in the ObservableCollection... But the items are of type Term, so how do I tell the xaml to access the IdText of the Term object and print that?
<CollectionView
ItemsSource="{Binding Terms}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Delete"
BackgroundColor="Red"/>
</SwipeItems>
</SwipeView.RightItems>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Edit"
BackgroundColor="Orange"/>
</SwipeItems>
</SwipeView.LeftItems>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<CollectionView
ItemsSource="{Binding Terms}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Delete"
BackgroundColor="Red"/>
</SwipeItems>
</SwipeView.RightItems>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Edit"
BackgroundColor="Orange"/>
</SwipeItems>
</SwipeView.LeftItems>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
I'm not sure how best to handle a "complex" object vs. just an ObservableCollection<string>. I essentially want the term.Name to be displayed for each Term object in the Collection, but when the term.Name is tapped in my app, I want it to reference pass the term.Id to an eventual call to open a CoursePage(term.Id)
108 Replies
leowest
leowest3mo ago
For a start, ObservableCollection does not required ObservableProperty, it already is an observable object.
public partial class TermsViewModel : ObservableObject
{
public TermsViewModel()
{
Terms = new ObservableCollection<Term>();
}

public ObservableCollection<Term> Terms { get; set; }
}
public partial class TermsViewModel : ObservableObject
{
public TermsViewModel()
{
Terms = new ObservableCollection<Term>();
}

public ObservableCollection<Term> Terms { get; set; }
}
In addition to that you should not need to specify the DataType and to bind the properties it would be as simple as using {Binding Id} or {Binding Name} https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/collectionview/populate-data?view=net-maui-8.0 https://learn.microsoft.com/en-us/dotnet/maui/xaml/fundamentals/data-binding-basics?view=net-maui-8.0 For more information these links should be of help
DaClownie
DaClownieOP3mo ago
Phenomenal, I’ll read up and use your information to start. I appreciate the time you put in to your answer and I’ll update if I make progress or hit snares. Thanks! Hey, so I'm hitting build time errors when trying to reference the Name and IdText fields of the Term objects inside the ObservableCollection we talked about Rider showed no issues until I tried to Build, visual studio on windows recognizes that it can't make the connection, but I can't quite figure out a way to resolve it
DaClownie
DaClownieOP3mo ago
No description
DaClownie
DaClownieOP3mo ago
I still can't figure out what I'm missing to make this work. The IDE knows its a Term, but at build time it tries to find the property IdText inside the TermsViewModel and not in the Term.
No description
DaClownie
DaClownieOP3mo ago
If I remove the IdText you see whats on the lines right below, Label "Text={Binding}" /> gets annotated by Rider with DataContext=Term
leowest
leowest3mo ago
can you show me your model preferable if u have a github its easier
DaClownie
DaClownieOP3mo ago
using System;
using System.Collections.Generic;

namespace LibrandriaMAUI.Models;

public partial class Term
{

public Guid Id { get; set; }

public string Name { get; set; } = null!;

public DateTime StartDate { get; set; }

public DateTime EndDate { get; set; }

public string UserId { get; set; }

public string? IdText { get; set; }

public DateTime DateCreated { get; set; }

public DateTime DateModified { get; set; }

private Term() { }

public Term(string name, DateTime startDate, DateTime endDate,
string userId)
{
Id = Guid.NewGuid();
Name = name;
StartDate = startDate;
EndDate = endDate;
UserId = userId;
IdText = Id.ToString();
}
}
using System;
using System.Collections.Generic;

namespace LibrandriaMAUI.Models;

public partial class Term
{

public Guid Id { get; set; }

public string Name { get; set; } = null!;

public DateTime StartDate { get; set; }

public DateTime EndDate { get; set; }

public string UserId { get; set; }

public string? IdText { get; set; }

public DateTime DateCreated { get; set; }

public DateTime DateModified { get; set; }

private Term() { }

public Term(string name, DateTime startDate, DateTime endDate,
string userId)
{
Id = Guid.NewGuid();
Name = name;
StartDate = startDate;
EndDate = endDate;
UserId = userId;
IdText = Id.ToString();
}
}
The github is set to private unless I can add specific users. I have to learn how to hide certain bits of info before i can make it public (connection strings, etc)
leowest
leowest3mo ago
and here are u binding via DI?
DaClownie
DaClownieOP3mo ago
probably just need to pan out a proper .gitignore but i haven't got there
leowest
leowest3mo ago
I mean u could make it public for a short period so I can clone it and then hide it again
DaClownie
DaClownieOP3mo ago
give me a moment
leowest
leowest3mo ago
for connection strings etc usually env vars are used or things u dont want to commit to your git and yes also a gitignore for obvious reasons because u dont want to push the bin folder etc https://www.jetbrains.com/help/rider/Run_Debug_Configuration.html#envvars-progargs
DaClownie
DaClownieOP3mo ago
check your messages sent it privately
leowest
leowest3mo ago
sadly I dont get dms, u can send here nad delete short after k
DaClownie
DaClownieOP3mo ago
tell me when i can delete and set private again :kek:
leowest
leowest3mo ago
see like u dont want ever to push these
No description
leowest
leowest3mo ago
u can try running a dotnet new gitignore that folder alone is probably making your project like 100~+ mbs to upload to git haha
DaClownie
DaClownieOP3mo ago
ok done, will it automatically remove things from the repo that is being ignored? or will I need to clean that up
leowest
leowest3mo ago
mmm it should does rider not have a git changes u can view?
DaClownie
DaClownieOP3mo ago
Just shows changed files, I'll deal with that part a bit later, add it to my todo list :kek:
leowest
leowest3mo ago
the binding looks fine, let me delve into I can also see u wired things right in the DI
DaClownie
DaClownieOP3mo ago
Ok, thats good to know I might have to rework how the database works, or how I interact with it. As it stands now, I need to add any IP that tries to connect to the Db to an allowed conneciton list. Probably need some sort of backend api that does the routing so its "local" (i'm spitballing, don't know what i'm talking about it, but assuming something like that is the proper methodology) I could also just host my own instance on my server, but I didn't want to spend all that time lol
leowest
leowest3mo ago
I mean depends is this a external database hosted somewhere?
DaClownie
DaClownieOP3mo ago
yep
leowest
leowest3mo ago
then u can't run it locally without creating your own
DaClownie
DaClownieOP3mo ago
I have an unRAID server that I could run a MySQL docker on
leowest
leowest3mo ago
you could dump all the scripts and create a local one for testing purpose if u want
DaClownie
DaClownieOP3mo ago
I figured just grabbing some web hosting would make my life simpler, but as i've been going, nothing has been easy lol
leowest
leowest3mo ago
also keep in mind this
leowest
leowest3mo ago
No description
leowest
leowest3mo ago
you dont use the private field Community Toolkit creates public ones for u to use so those should be in your GetUserId Username and Password
leowest
leowest3mo ago
No description
DaClownie
DaClownieOP3mo ago
ohhhh right
leowest
leowest3mo ago
and for your models
DaClownie
DaClownieOP3mo ago
I also know there's no hashing or salting of passwords happening, I was going to just roll that in after
leowest
leowest3mo ago
if u have a string that will never be empty you can append required to it i.e.:
leowest
leowest3mo ago
No description
leowest
leowest3mo ago
No description
DaClownie
DaClownieOP3mo ago
I did have a question just on best practice while I have you here... Currently I have the db creating the DateCreated, DateModified fields when the database is added to or updated by EF, should I have all that logic on the c# side of things and remove that logic from the db?
leowest
leowest3mo ago
ok I see the problem u will have to define the datatemplate type
<DataTemplate x:DataType="model:Term">
<DataTemplate x:DataType="model:Term">
and
xmlns:model="clr-namespace:LibrandriaMAUI.Models"
xmlns:model="clr-namespace:LibrandriaMAUI.Models"
I usually let the db handle those also u should use DateTimeOffset instead
DaClownie
DaClownieOP3mo ago
damnit ok. I had x:DataType="string" in there before from the example and I was like well i'm not just displaying strings, but the IDE was not mad about anything so I forgot I did that lol oh for UTC time
leowest
leowest3mo ago
$datetimelie
MODiX
MODiX3mo ago
please please please get into the habit of using DateTimeOffset instead of DateTime in your code everywhere you possibly can. DateTimeOffset contains the UTC offset with the data so is lossless. DateTime is very very weird in how it stores data. DateTime only knows UTC and "local" time. But your local time zone can change! Laptops easily move across state lines, even while an app is currently running. (So you are in California running an app, put some DateTime instance in memory, hop on a plane to New York, then resume your app 3 time zones ahead. What on earth will be contained within the DateTime instance?) But wait, this was a lie. DateTime actually has 3 ways it keeps track of time: UTC, local (which can change while an app is running), and unspecified (unknown, essentially). (Unknown = I don't know if it's UTC or local, the app will figure it out later and call ToUniversal or ToLocal before calling ToString or any comparison routine.) But wait, this was a lie. DateTime actually has a secret fourth way of storing data that's not exposed in the standard API surface! It's truly an abomination of a type. All of this nonsense is irrelevant if we just pretend DateTime doesn't exist and we instead use DateTimeOffset everywhere.
- GrabYourPitchforks (start: https://discord.com/channels/143867839282020352/143867839282020352/988353756108312607)
leowest
leowest3mo ago
also please 👆 you really should not mark it as observable also instead of using ObservableObject you should create a common class to inherit from i.e.:
public partial class BaseViewModel : ObservableObject;
public partial class BaseViewModel : ObservableObject;
DaClownie
DaClownieOP3mo ago
ah, that was how the example I followed did it, I can revise Is this so if I need to make changes to how I make models, I can change all at once, but they all inherit the ObservableObject if they inherit BaseViewModel? Ok, let me fix the original databinding issue and then I'll continue reading this, gimme a minute
leowest
leowest3mo ago
and last but not least for organizational purposes for EF core I prefer keeping the Entity configuration next to the class by using IEntityTypeConfiguration, here is an example:
public class Term
{
public Guid Id { get; set; }

public string Name { get; set; } = null!;

public DateTime StartDate { get; set; }

public DateTime EndDate { get; set; }

public required string UserId { get; set; }

public string? IdText { get; set; }

public DateTime DateCreated { get; set; }

public DateTime DateModified { get; set; }
}

public class TermConfigurator : IEntityTypeConfiguration<Term>
{
public void Configure(EntityTypeBuilder<Term> entity)
{
entity.HasKey(e => e.Id).HasName("PRIMARY");

entity.Property(e => e.DateCreated)
.HasDefaultValueSql("CURRENT_TIMESTAMP")
.HasColumnType("datetime");
entity.Property(e => e.DateModified)
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("datetime");
entity.Property(e => e.EndDate).HasColumnType("datetime");
entity.Property(e => e.IdText)
.HasMaxLength(36)
.HasComputedColumnSql("insert(insert(insert(insert(hex(`id`),9,0,_utf8mb4'-'),14,0,_utf8mb4'-'),19,0,_utf8mb4'-'),24,0,_utf8mb4'-')", false);
entity.Property(e => e.Name).HasMaxLength(64);
entity.Property(e => e.StartDate).HasColumnType("datetime");
entity.Property(e => e.UserId).HasMaxLength(36);
}
}
public class Term
{
public Guid Id { get; set; }

public string Name { get; set; } = null!;

public DateTime StartDate { get; set; }

public DateTime EndDate { get; set; }

public required string UserId { get; set; }

public string? IdText { get; set; }

public DateTime DateCreated { get; set; }

public DateTime DateModified { get; set; }
}

public class TermConfigurator : IEntityTypeConfiguration<Term>
{
public void Configure(EntityTypeBuilder<Term> entity)
{
entity.HasKey(e => e.Id).HasName("PRIMARY");

entity.Property(e => e.DateCreated)
.HasDefaultValueSql("CURRENT_TIMESTAMP")
.HasColumnType("datetime");
entity.Property(e => e.DateModified)
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("datetime");
entity.Property(e => e.EndDate).HasColumnType("datetime");
entity.Property(e => e.IdText)
.HasMaxLength(36)
.HasComputedColumnSql("insert(insert(insert(insert(hex(`id`),9,0,_utf8mb4'-'),14,0,_utf8mb4'-'),19,0,_utf8mb4'-'),24,0,_utf8mb4'-')", false);
entity.Property(e => e.Name).HasMaxLength(64);
entity.Property(e => e.StartDate).HasColumnType("datetime");
entity.Property(e => e.UserId).HasMaxLength(36);
}
}
and then you can group call all of those like this
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.UseSerialColumns();
modelBuilder.ApplyConfigurationsFromAssembly(typeof(LibrandriaDbContext).Assembly);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.UseSerialColumns();
modelBuilder.ApplyConfigurationsFromAssembly(typeof(LibrandriaDbContext).Assembly);
}
yes but also more than that you could have a ItemsSources made of BaseViewModel and then changing a page based on what u select from that menu and it would wire the view so there is more to it
leowest
leowest3mo ago
this is what I wanna see next in your project
No description
leowest
leowest3mo ago
:Ok:
DaClownie
DaClownieOP3mo ago
Your expectations of me are unrealistic, but i love you for it :kek:
leowest
leowest3mo ago
not at all, in this time we've been talking I did it and I am sure u can aswell there was nothing overcomplex I had to do to achieve that
DaClownie
DaClownieOP3mo ago
Would you want to look at one other part? I don't have the change up on github yet because I was trying to figure out how to do it
leowest
leowest3mo ago
what would that be
DaClownie
DaClownieOP3mo ago
So in the constructor for TermsViewModel() i have Terms = new ObservableCollection<Term>; I wanted to populate that ObservableCollection with the LibrandriaManager method for public async Task<List<Term> LoadTermList()
leowest
leowest3mo ago
ah good I also wanted to talk about your SQLFunctions instead you should create services and take advantage of the DI that would be the proper way MVVMS
DaClownie
DaClownieOP3mo ago
but it returns a type Task<List<Term>> which can't be used in the new ObservableCollection<Term>(LoadTermList());
leowest
leowest3mo ago
well its an async method u have to await
DaClownie
DaClownieOP3mo ago
oh jesus christ
leowest
leowest3mo ago
but either way u dont want to do that in the constructor loading anything in the constructor is not good what u should do is
DaClownie
DaClownieOP3mo ago
I can put the await in those parentheses can't I
leowest
leowest3mo ago
in the ContentPage you create a Loaded event then you wire that to your vm
DaClownie
DaClownieOP3mo ago
oh in the code behind? I was in the process of phasing the SQLFunctions out for instancing LibrandriaManager to remove all the static nonsense i was doing
leowest
leowest3mo ago
so it somewhat looks like this
No description
leowest
leowest3mo ago
yes and u do that with services
DaClownie
DaClownieOP3mo ago
So I'd create a builder.Services.AddSingleton<LibrandriaManager>(); in my MauiProgram.cs?
leowest
leowest3mo ago
for example:
public class UserService
{
private readonly LibrandriaDbContext _database;
public UserService(LibrandriaDbContext database)
{
_database = database;
}

public async Task<User> GetUser(int id)
{
return await _database.FirstOrDefaultAsync(x => x.Id == id);
}
}
public class UserService
{
private readonly LibrandriaDbContext _database;
public UserService(LibrandriaDbContext database)
{
_database = database;
}

public async Task<User> GetUser(int id)
{
return await _database.FirstOrDefaultAsync(x => x.Id == id);
}
}
then you register it in your DI and you can pass that to your ViewModel in the constructor and have access to it in the same way you already do LibrandriaDbContext
DaClownie
DaClownieOP3mo ago
I have a LibrandriaDbContext in there, but not the LibrandriaManager
leowest
leowest3mo ago
beyond that u would create specific services for each model as needed I would not make a Manager that does everything
DaClownie
DaClownieOP3mo ago
ohhh, get rid of a central file idea entirely
leowest
leowest3mo ago
yes it would be just a massive confusing file also SRP
DaClownie
DaClownieOP3mo ago
So I should create a new Folder for Services, and create a Service for each ViewModel that needs it
leowest
leowest3mo ago
yes u could do that I prefer using a VSA approach here because its easier to manage everything from a organizational PoV
DaClownie
DaClownieOP3mo ago
whats VSA
leowest
leowest3mo ago
$vsa
leowest
leowest3mo ago
Vertical Slice Architecture
DaClownie
DaClownieOP3mo ago
gotcha, I'll add that to my reading list
leowest
leowest3mo ago
so instead of having Models, ViewModels, Services yada yada
DaClownie
DaClownieOP3mo ago
TermModel, TermViewModel, TermService, TermPage something like that?
leowest
leowest3mo ago
no anyway dont worry about that right now you dont need to append Model to your models
DaClownie
DaClownieOP3mo ago
haha ok
leowest
leowest3mo ago
u would append DTO to your dtos
DaClownie
DaClownieOP3mo ago
$dto no? worth a shot
leowest
leowest3mo ago
:when:
DaClownie
DaClownieOP3mo ago
:kek:
leowest
leowest3mo ago
DTOs are used to transport only the data u need so you're not always querying your whole table and also to improve security of EF when using tracking for example
DaClownie
DaClownieOP3mo ago
I've got such a long way to go :kek: Ok, I need to tackle a few things here at work, and then I'm going to dig into everything we covered
leowest
leowest3mo ago
if I have a dashboard page and all I need there for the user is the username id and email then I can do something like model
public class User
{
public int Id {get;set;}
public required string Username {get;set;}
public required string Email {get;set;}
public int Age {get;set;}
public string Biography {get;set;}
// .. lots of other props ..
}
public class User
{
public int Id {get;set;}
public required string Username {get;set;}
public required string Email {get;set;}
public int Age {get;set;}
public string Biography {get;set;}
// .. lots of other props ..
}
Then I need Username, Email and Id
public class UserDto
{
public int Id {get;set;}
public string Name {get;set;}
public string Email {get;set;}
}
public class UserDto
{
public int Id {get;set;}
public string Name {get;set;}
public string Email {get;set;}
}
Then in some service I would have something like
public class UserService
{
private readonly DatabaseContext _db;
public UserService(DatabaseContext db)
{
_db = db;
}

public async Task<UserDto> GetUser(int id)
{
return await _db.Users
.Select(x => x.ToUserDto())
.FirstOrDefaultAsync(u => u.Id == id);
}
}
public class UserService
{
private readonly DatabaseContext _db;
public UserService(DatabaseContext db)
{
_db = db;
}

public async Task<UserDto> GetUser(int id)
{
return await _db.Users
.Select(x => x.ToUserDto())
.FirstOrDefaultAsync(u => u.Id == id);
}
}
Then in your ViewModel it would look somewhat
public class DashboardViewModel : BaseViewModel
{
private readonly UserService _userService;
public DashboardViewModel(UserService userService)
{
_userService = userService
}

[ObservableProperty]
private UserDto _currentUser;

public async Task OnLoad()
{
CurrentUser = await _userService.GetUser(1);
}
}
public class DashboardViewModel : BaseViewModel
{
private readonly UserService _userService;
public DashboardViewModel(UserService userService)
{
_userService = userService
}

[ObservableProperty]
private UserDto _currentUser;

public async Task OnLoad()
{
CurrentUser = await _userService.GetUser(1);
}
}
and subsequently that generates a SQL query like
SELECT id, username, email FROM users WHERE id = 1
SELECT id, username, email FROM users WHERE id = 1
instead of:
SELECT * FROM users WHERE id = 1
SELECT * FROM users WHERE id = 1
Which is more performant if u have lots of columns https://learn.microsoft.com/en-us/ef/core/performance/efficient-querying#project-only-properties-you-need so imagine u are serving the whole User to your view, now if I edit your app memory for Role and change it to Admin, and update my user I gain admin access because you're passing User around and its being tracked by EF. so that is one of the security issues DTOs tackle if your models were all readonly for example it wouldn't be an issue this is more common in webapis thou anyway hf I g2g
DaClownie
DaClownieOP3mo ago
Thanks for everything! I’ll read up on all of this tonight Ok, I've been able to incorporate basically everything you've said except for this ContentPage_Loaded method. Not quite sure how to handle that yet. But I've eliminated SQLFunctions and DataObjects entirely. No more globally referenced static things laying around. Everything is through DI
leowest
leowest3mo ago
add that to your content page then click the red squiggle and see what options it gives u
DaClownie
DaClownieOP3mo ago
wants me to just create a method in TermsViewModel for OnLoaded
leowest
leowest3mo ago
:Ok:
DaClownie
DaClownieOP3mo ago
Is that where I'd plug in the code to populate the Terms list?
leowest
leowest3mo ago
yes in the vm method which u can make async Task
DaClownie
DaClownieOP3mo ago
k lemme sort out all the stuff happening It doesn't want me to assign Terms a value in a static method, and I need to convert the List<Term> to ObservableCollection which I had done before I believe, just need to look through my code somewhere :kek: Ok so my TermService has this:
public class TermService(LibrandriaDbContext context, UserService userService)
{
public Term? CurrentTerm { get; set; }
public async Task<List<Term>> GetTermList()
{
return await context.Terms
.Where(t => t.UserId == userService.CurrentUser!.IdText).ToListAsync();
}
...
public class TermService(LibrandriaDbContext context, UserService userService)
{
public Term? CurrentTerm { get; set; }
public async Task<List<Term>> GetTermList()
{
return await context.Terms
.Where(t => t.UserId == userService.CurrentUser!.IdText).ToListAsync();
}
...
TermsPage.xaml.cs has:
public TermsViewModel TermsViewModel => (TermsViewModel)BindingContext;
public TermsPage(TermsViewModel vm)
{
InitializeComponent();
BindingContext = vm;
}

private async void ContentPage_Loaded(object sender, EventArgs e)
{
await TermsViewModel.OnLoaded();
}
public TermsViewModel TermsViewModel => (TermsViewModel)BindingContext;
public TermsPage(TermsViewModel vm)
{
InitializeComponent();
BindingContext = vm;
}

private async void ContentPage_Loaded(object sender, EventArgs e)
{
await TermsViewModel.OnLoaded();
}
TermsViewModel has:
public partial class TermsViewModel(TermService termService) : BaseViewModel
{
public ObservableCollection<Term> Terms { get; set; }

public static async Task OnLoaded()
{
var term = await termService.GetTermList();
}
}
public partial class TermsViewModel(TermService termService) : BaseViewModel
{
public ObservableCollection<Term> Terms { get; set; }

public static async Task OnLoaded()
{
var term = await termService.GetTermList();
}
}
It says I can't use the primary constructor termService in that context, but then if I change it to TermService.GetTermList(); it says I can't access the static method of GetTermList(), but if I make GetTermList() static, it won't allow me to use my context from my constructor there I'm going in a loop :kek: Ok... so I had to move (TermService termService) out of the primary constructor and make this: ```cs public partial class TermsViewModel : BaseViewModel { private static TermService _termService; public TermsViewModel(TermService termService) { _termService = termService; } public static async Task OnLoaded() { var term = await _termService.GetTermList(); Terms = new ObservableCollection<Term>(term); } ``` Now everything seems to be happy? I can probably remove the whole var term thing there and make that a single line, lets see if it populates my terms first Where do I actually call the ContentPage_Loaded` method, and what am I using for the sender and args when I do?
leowest
leowest3mo ago
I mean the code is very clear where it is at, you can see TermsPage and u also know what a ContentPage is also why the heck are u using static everywhere dont there is no code that needs static in anything I showed u earlier stop recurring to static for everything its bad
DaClownie
DaClownieOP3mo ago
I couldn’t call the TermsViewModel.OnLoaded. Rider threw an error because it wasn’t a static method in the VM
leowest
leowest3mo ago
no it threw an error because u did not write it like in my example also I admit the naming was ambiguous there should have made it simpler with
private readonly TermsViewModel _termsViewModel;

public TermsPage(TermsViewModel vm)
{
_termsViewModel = vm;
InitializeComponent();
BindingContext = _termsViewModel;
}
private readonly TermsViewModel _termsViewModel;

public TermsPage(TermsViewModel vm)
{
_termsViewModel = vm;
InitializeComponent();
BindingContext = _termsViewModel;
}
so u would call _termsViewModel.OnLoaded(); as it was with the ambiguous name u would probably have to use this.TermsViewModel so it understand it was referring to the instance of TermsViewModel and not the actual type
DaClownie
DaClownieOP3mo ago
Ok, I’m about to drive home but I’ll take a quick stab at it before I go to bed and see if I can’t get it working like we want I should have noticed from the color coding from your visual studio screenshot
leowest
leowest3mo ago
no worries I did make it confusing by naming it the same as the type
DaClownie
DaClownieOP3mo ago
I’ve been up close to 24 hours :kekw:
leowest
leowest3mo ago
either way excluding the statics it all looks fine
DaClownie
DaClownieOP3mo ago
Very cool, greatly appreciated Been taking a lot of time to read docs and things to understand the guidance better. Don’t wanna just copy it and not understand what it’s doing
leowest
leowest3mo ago
just side note I wouldn't make this a dependency
public class TermService(LibrandriaDbContext context, UserService userService)
{
public Term? CurrentTerm { get; set; }
public async Task<List<Term>> GetTermList()
{
return await context.Terms
.Where(t => t.UserId == userService.CurrentUser!.IdText).ToListAsync();
}
public class TermService(LibrandriaDbContext context, UserService userService)
{
public Term? CurrentTerm { get; set; }
public async Task<List<Term>> GetTermList()
{
return await context.Terms
.Where(t => t.UserId == userService.CurrentUser!.IdText).ToListAsync();
}
if all u need to pass in is an id I would rather pass the id and decouple it GetTermsByUser(int id) or ForUser(int id) or w/e the type it is it makes more sense understandable, and yes its very good that you're taking that time on top of practicing and doing it makes it click faster
DaClownie
DaClownieOP3mo ago
Ok, I got everything working Interestingly enough I could not get my interface to update unless I changed back to this:
[ObservableProperty]
private ObservableCollection<Term> _terms;
[ObservableProperty]
private ObservableCollection<Term> _terms;
I know you had mentioned that it was already Observable and not necessary, but I went through all of the breakpoints, saw that the method was being called, the list was being populated, the list was being passed to the ObservableCollection<Term> Terms but it just wasn't drawing on the UI. For shits and giggles I went back to this just to see if it changed anything (thinking it wouldn't, but eliminating all things) and it worked.
leowest
leowest3mo ago
if it was not updating something was wrong because I can 100% guarantee you that does not need it that is the one type that doesnt need it
DaClownie
DaClownieOP3mo ago
Yea the only change I made to make everything display was add [ObservableProperty]. Weird right? lol My next issue is handling popups, but my google searches are showing that it might in fact be a bug in MAUI with iOS/MacOS, otherwise though I'm cruising along at the moment Got that resolved now a well forging along. If I get to the end and have time, i'll dig into the observable property on the collection issue, but not a big deal atm
leowest
leowest3mo ago
$close
MODiX
MODiX3mo ago
If you have no further questions, please use /close to mark the forum thread as answered
Want results from more Discord servers?
Add your server