✅ 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:
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?
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
For a start, ObservableCollection does not required ObservableProperty, it already is an observable object.
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 helpPhenomenal, 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
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.
If I remove the IdText you see whats on the lines right below,
Label "Text={Binding}" />
gets annotated by Rider with DataContext=Term
can you show me your model
preferable if u have a github its easier
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)
and here are u binding via DI?
probably just need to pan out a proper .gitignore but i haven't got there
I mean u could make it public for a short period so I can clone it and then hide it again
give me a moment
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
check your messages
sent it privately
sadly I dont get dms, u can send here nad delete short after
k
tell me when i can delete
and set private again
:kek:
see like u dont want ever to push these
u can try running a
dotnet new gitignore
that folder alone is probably making your project like 100~+ mbs to upload to git hahaok done, will it automatically remove things from the repo that is being ignored? or will I need to clean that up
mmm it should does rider not have a git changes u can view?
Just shows changed files, I'll deal with that part a bit later, add it to my todo list :kek:
the binding looks fine, let me delve into
I can also see u wired things right in the DI
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
I mean depends is this a external database hosted somewhere?
yep
then u can't run it locally without creating your own
I have an unRAID server that I could run a MySQL docker on
you could dump all the scripts and create a local one for testing purpose if u want
I figured just grabbing some web hosting would make my life simpler, but as i've been going, nothing has been easy lol
also keep in mind this
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
ohhhh right
and for your models
I also know there's no hashing or salting of passwords happening, I was going to just roll that in after
if u have a string that will never be empty
you can append required to it
i.e.:
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?ok I see the problem u will have to define the datatemplate type
and
I usually let the db handle those
also u should use DateTimeOffset instead
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$datetimelie
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)
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.:
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
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:
and then you can group call all of those like this
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
this is what I wanna see next in your project
:Ok:
Your expectations of me are unrealistic, but i love you for it
:kek:
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
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
what would that be
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()
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
but it returns a type
Task<List<Term>>
which can't be used in the new ObservableCollection<Term>(LoadTermList());
well its an async method u have to await
oh jesus christ
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
I can put the await in those parentheses can't I
in the ContentPage you create a Loaded event
then you wire that to your vm
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 doingso it somewhat looks like this
yes
and u do that with services
So I'd create a
builder.Services.AddSingleton<LibrandriaManager>();
in my MauiProgram.cs?for example:
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
I have a
LibrandriaDbContext
in there, but not the LibrandriaManager
beyond that u would create specific services
for each model as needed
I would not make a Manager that does everything
ohhh, get rid of a central file idea entirely
yes it would be just a massive confusing file
also SRP
So I should create a new Folder for Services, and create a Service for each ViewModel that needs it
yes u could do that
I prefer using a VSA approach here
because its easier to manage everything from a organizational PoV
whats VSA
$vsa
Vertical Slice Architecture
gotcha, I'll add that to my reading list
so instead of having Models, ViewModels, Services yada yada
TermModel, TermViewModel, TermService, TermPage
something like that?
no
anyway dont worry about that right now
you dont need to append Model to your models
haha ok
u would append DTO to your dtos
$dto
no? worth a shot
:when:
:kek:
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
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
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
Then I need Username, Email and Id
Then in some service I would have something like
Then in your ViewModel it would look somewhat
and subsequently that generates a SQL query like
instead of:
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
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 DIadd that to your content page then click the red squiggle and see what options it gives u
wants me to just create a method in TermsViewModel for OnLoaded
:Ok:
Is that where I'd plug in the code to populate the Terms list?
yes in the vm method
which u can make async Task
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:
TermsPage.xaml.cs has:
TermsViewModel has:
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?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
I couldn’t call the TermsViewModel.OnLoaded. Rider threw an error because it wasn’t a static method in the VM
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
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 typeOk, 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
no worries I did make it confusing by naming it the same as the type
I’ve been up close to 24 hours :kekw:
either way excluding the statics
it all looks fine
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
just side note I wouldn't make this a dependency
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
Ok, I got everything working
Interestingly enough
I could not get my interface to update unless I changed back to this:
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.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
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
$close
If you have no further questions, please use /close to mark the forum thread as answered