Xamarin Forms ListView not Updating from ObservableCollection - xamarin

The ListView of my XAML file is being filled with a ViewModel that has an ObservableCollection from a service but the ListView is not showing the information. I already check that the service is returning the correct information.
This is the code of my XML file :
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XAMLUnit1.Views.NetflixRoulettePage" Title="Movie">
<ContentPage.Content>
<StackLayout>
<AbsoluteLayout>
<BoxView Color="Gray" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1"></BoxView>
<SearchBar x:Name="searchBar"
Placeholder="Search By Actor's name"
PlaceholderColor="White"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0.1,0.1,1,1" SearchCommand="{Binding SearchMovieCommand}" ></SearchBar>
</AbsoluteLayout>
<ListView x:Name="ListOfMovies"
ItemsSource="{ Binding MovieList}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<ImageCell
ImageSource="{Binding poster_path, StringFormat='https://image.tmdb.org/t/p/w500{0}'}">
</ImageCell>
<StackLayout Orientation="Horizontal">
<TextCell Detail="{Binding title}"></TextCell>
<TextCell Detail="{Binding release_date}"></TextCell>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
This is the ViewModel that calls the service and it's uses its ObservableCollection as ItemsSource for the ListView :
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using XAMLUnit1.Models;
using XAMLUnit1.ServiceImpl;
using XAMLUnit1.Services;
namespace XAMLUnit1.ViewModels
{
public class MovieRouletteViewModel
{
IMovieService service;
public ObservableCollection<Movie> MovieList { get; set; }
public ICommand SearchMovieCommand { get; set; }
public MovieRouletteViewModel()
{
service = new MovieServiceFinder();
SearchMovieCommand = new Command(GetMovies);
}
private void GetMovies()
{ var list= service.GetMovies("");
MovieList = list;
}
}
}
public partial class NetflixRoulettePage : ContentPage
{
MovieRouletteViewModel viewModel;
public NetflixRoulettePage()
{
InitializeComponent();
viewModel = new MovieRouletteViewModel();
BindingContext = viewModel;
}
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
{
}
}

Do not set the ObservableCollection to a new List it will break the binding. Clear the items from the list and add the new items to it.
public MovieRouletteViewModel()
{
service = new MovieServiceFinder();
SearchMovieCommand = new Command(GetMovies);
MovieList = new ObservableCollection<Movie>();
}
private void GetMovies()
{
var list= service.GetMovies("");
MovieList.Clear();
foreach(Movie movie in list)
{
MovieList.Add(movie);
}
}

There is a few things i want to note on this problem, though mainly as the comments rightly illuminate, replacing the collection with a new collection breaks the bindings. hence why its not updating.
However there are several solutions to consider.
Implement INotifyPropertyChanged on the collection and replace the whole collection like you are doing, This will work fine. However it does resets the scroll and basically recalculates everything from scratch. Some would say this defeats the point of an ObservableCollection as they have their own built in notification plumbing, Yeah it does. Though, if you are replacing the whole Collection with a new collection then you are going to save on oodles of Cyclic calculations when compared to cleaRing and adding each item back individually Which will basically fire for every update
Call Clear and Add on each item individually.. If the collection hasn't changed much, you can even take it a step further by just comparing the 2 collections and updating whats needed. However once again, if the collections are dissimilar, then this approach is still expensive, and on a mobile device you want to minimize screen recalculations where ever possible .
Create a subclassed collection, implement your own replace/update, and Add/Remove range methods, and INotifyPropertyChanged giving you the best of both worlds, this will allow atomic modification of the collection and then you can fire property changed event once.
There are many examples of all these approaches online, its just worth noting clearing and adding items sometimes is not the best approach for mobile devices. It depends how big your collections are, how much is changing and why.
If it is a completely different list, then replacing the collection is fine in my opinion.
If its the same list, then well you may want to compare and modify to save on property changes
Anyway good luck

I disagree with other answers. You can set the Movies as many times as you want, this is not the problem.
The problem is just that your viewmodel doesn't implement INotifyPropertyChanged.
Your UI is just not notified when you set your ObservableCollection.

Your service command is actually replacing the ObservableCollection, you need to change your GetMovies() method to
var list = service.GetMovies("");
MovieList.clear();
foreach(Movie m in list)
{ MovieList.Add(m);}

Related

Why does the "static" keyword makes the difference in CollectionView?

I've built a CollectionView with Maui, this is the code:
//if I do remove this "static" keyword the code doesn't work anymore.
public static ObservableCollection<Test> _testing = new
ObservableCollection<Test>();
public ObservableCollection<Test> Testing
{
get => _testing;
set { _testing = value; OnPropertyChanged(nameof(Test)); }
}
and here I add new data:
public IndexTest()
{
Testing.Add(new Test{ name = "hello" });
}
This is the xaml:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="OpenToursNative.Index"
xmlns:vm="clr-namespace:OpenToursNative.Test">
<ContentPage.BindingContext>
<vm:IndexTest/>
</ContentPage.BindingContext>
<CollectionView ItemsSource="{Binding Test}">
<CollectionView.ItemTemplate>
<DataTemplate>
<LabelText="{Binding name}"></Label>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
If I run this code, everything works without problem, but, if I remove the "static" keyword on the ObservableCollection it doesn't log anything anymore.
Through the debugging it seems that every time I load the page the List counts 1 item, then after it loaded the List counts is 0.
I've wasted few days looking for a reason why it wasn't working at the beginning, adding "static" did the trick.
Can anyone explain to me why does the "static" keyword make this change?
Thank you all.
The meaning of static is fundamental to understanding c# code. It means that all the instances of that class share a single member (variable) with that name. Therefore, each time you navigate, that variable still contains whatever it had before.

Any alternatives to the switching different groups of Page elements visibility in .net Maui?

I need to build a page which has some static "always there" elements, and some alternate groups of elements that show up and hide depending on user actions.
One approach could be combine each group of elements into some container like StackLayout and control their visibility, like this:
<StackLayout IsVisible="{Binding IsLoaded}">...</StackLayout>
<StackLayout IsVisible="{Binding IsLoaded}, Converter={helpers:InverseBoolConverter}">...</StackLayout>
However if there are more than 2 such groups of such elements, more overhead is added, and I feel like a better way of implementing that must exist.
What I've found is DataTemplateSelector, however: it seems to work for "list of items" type of controls. I wonder if something similar exists for a ContentPage or non-list controls, so I can define 2 or more alternative templates (or controls) with bindings to the same ViewModel inside, and based on the page ViewModel data switch their visibility: ViewModel.IsLoaded=true display one template/control, otherwise display the other.
Note: different control styling won't be enough for my scenario, it's different set of controls.
StackLayout can specify a DataTemplateSelector in its BindableLayout.ItemTemplateSelector:
<StackLayout BindableLayout.ItemsSource="{Binding Source}"
BindableLayout.ItemTemplateSelector="{StaticResource TemplateSelector}"/>
BindableLayout.ItemsSource might be a collection containing one item:
Source = new ObservableCollection<Model>
{
model
};
The DataTemplateSelector might be triggered as follows:
Source.Clear();
Source.Add(model);
Sample DataTemplateSelector:
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate DataTemplate1 { get; set; }
public DataTemplate DataTemplate2 { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
Model model = item as Model;
// Determine template; DataTemplate1 or DataTemplate2
return template;
}
}
Sample usage in XAML:
<DataTemplate x:Key="DataTemplate1">
...
<DataTemplate x:Key="DataTemplate2">
...
<namespace:TemplateSelector x:Key="TemplateSelector"
DataTemplate1="{StaticResource DataTemplate1}"
DataTemplate2="{StaticResource DataTemplate2}" />

super Curstom value converters in Xamarin

I have a Xamarin forms app where I exhibit the result from different sensors all in one ListView. Every sensor populates a 'DataTemplate' ViewCell where I show the name, location and others and, in the central grid the values.
The point is that the visualization for those values should be different from each sensor (imagine I want a moving arrow for the wind, a growing blue box for the collected water, a number for the temperature, and so on)
Is it possible to return a custom UIElement, or a Grid or something from a IValueConverter and be able to do this task ?
If not, what would you recommend ?
PS: I think that, what I want to do, is done via ContentPresenter. but .. I cannot find proper details on how to achieve it.
Sounds like DataTemplateSelector should solve your issue:
A DataTemplateSelector can be used to choose a DataTemplate at runtime
based on the value of a data-bound property. This enables multiple
DataTemplates to be applied to the same type of object, to customize
the appearance of particular objects. This article demonstrates how to
create and consume a DataTemplateSelector.
Official docs: Creating a Xamarin.Forms DataTemplateSelector
Creating a DataTemplateSelector:
public class PersonDataTemplateSelector : DataTemplateSelector
{
public DataTemplate ValidTemplate { get; set; }
public DataTemplate InvalidTemplate { get; set; }
protected override DataTemplate OnSelectTemplate (object item, BindableObject container)
{
return ((Person)item).DateOfBirth.Year >= 1980 ? ValidTemplate : InvalidTemplate;
}
}
Consuming a DataTemplateSelector in XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Selector;assembly=Selector" x:Class="Selector.HomePage">
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="validPersonTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="invalidPersonTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<local:PersonDataTemplateSelector x:Key="personDataTemplateSelector"
ValidTemplate="{StaticResource validPersonTemplate}"
InvalidTemplate="{StaticResource invalidPersonTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>
<ListView x:Name="listView" ItemTemplate="{StaticResource personDataTemplateSelector}" />

How to preview an ItemTemplate in the XAML designer

Say I have a simple ItemTemplate for a ListView
<ListView ItemsSource="{x:Bind ListItems}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:TextItem">
<TextBlock Text="{x:Bind Item}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
MainPage.xaml
When I run the app, I get a ListView populated with TextBlocks - one for each item in ListItems (which is a behind defined variable). However, in the XAML designer, I see nothing.
So, is there a way to preview the ItemTemplate/DataTemplate in the XAML designer, with a set number of placeholder TextBlocks with placeholder text replacing the Text="{x:Bind Item}"? Or just preview a single TextBlock?
This is not a duplicate of
Design View of a DataTemplate in a ResourceDictionary in Visual Studio - because I can't use Blend. Everything I've looked up that says use Blend in a certain way merely gives me the message 'This feature is not available for projects targeting "Windows 10 Fall Creators Update (10.0; Build 16299)"'
Can I preview my DataTemplate(s) in my collection-based controls in the designer? - well, perhaps it is, but I don't understand the accepted answer, which is now several years old. An answer containing an example tailored to my question would be really helpful.
Microsoft actually posted a (semi helpful) tutorial on this in April of this year: link
Requirements
Whatever you want to display, you will need this at the top of your XAML code (should be there by default if you created the page using VS's templates):
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
A simple list of strings
For something simple like a list of strings, something like this will work just fine (taken from the above mentioned MSDN page):
<ListView>
<d:ListView.Items>
<system:String>Item One</system:String>
<system:String>Item Two</system:String>
<system:String>Item Three</system:String>
</d:ListView.Items>
</ListView>
Display something more complex
If you want to use a data source from somewhere else in your code, it gets a little more ugly:
First you need to set your data context (usually done at the page level, but you can also add it just on the element where you want to use it):
d:DataContext="{d:DesignInstance
yournamespace:YourDataSourceClass,
IsDesignTimeCreatable=True}"
And on the ListView add:
d:ItemsSource="{Binding data}"
If you are using an ItemTemplate, you will need to add "d:" variants for everything you want to pull from your data source.
Full example code:
XAML:
<Page
x:Class="exampleApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:exampleApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:DesignTimeData,
IsDesignTimeCreatable=True}"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid>
<ListView ItemsSource="{x:Bind ListItems}" d:ItemsSource="{Binding DummyData}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:TextItem">
<TextBlock Text="{x:Bind Item}" d:Text="{Binding Item}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
</Page>
C#:
using System.Collections.ObjectModel;
using Windows.UI.Xaml.Controls;
namespace exampleApp {
public sealed partial class MainPage : Page {
public ObservableCollection<TextItem> ListItems;
public MainPage() {
this.InitializeComponent();
//populate you ListItems list
}
}
public class TextItem {
public string Item { get; set; }
}
public class DesignTimeData {
public ObservableCollection<TextItem> DummyData { get; set; } = new ObservableCollection<TextItem> {
new TextItem { Item = "foo" },
new TextItem { Item = "bar" },
new TextItem { Item = "bla" },
new TextItem { Item = "blubb" }
};
}
}
Remarks
It seems you have to use "Binding" rather than "x:Bind" for the design-time bindings. From what I understand, x:Bind works using code generated at compile time, which is not executed by the XAML designer
When you change your Binding declarations or data source code, you will have to recompile to see the changes in the designer. Other design changes will reflect in real time
Binding is more limited than x:Bind in some regards (notably you cannot bind to class methods using Binding, only x:Bind). Keep that in mind when writing your design-time data
Sadly some of the more convenient ways of getting design-time data into your app are WPF and Xamarin only (Blends' data window, data declared in XAML file, and especially auto-generated sample data)
(Everything was tested using Visual Studio 2019 version 16.10.3.)

how to detect when data binding to UI elements (panorama and listbox) have completed in windows phone 7?

i have a Panorama control and ListBox controls inside the Panorama. is there any "event" that i can hook on to or any way to detect when all the data binding or UI display associated with the Panorama and/or ListBox controls are finished?
the reason i need to detect this event is because i want to show the ApplicationBar only after the Panorama and/or ListBox controls have completely binded and finished rendering.
for example, my XAML is defined as the following.
<controls:Panorama Name="panorama">
<controls:Panorama.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Details}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Field1}"/>
<TextBlock Text="{Binding Field2}"/>
...
<TextBlock Text="{Binding FieldN}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</controls:Panorama.ItemTemplate>
</controls:Panorama>
my plain-old CLR object (POCO) looks like the following.
public class MyPoco {
List<Detail> Details { get; set; }
}
public class Detail {
public string Field1 { get; set; }
public string Field1 { get; set; }
...
public string FieldN { get; set; }
}
in my c# code-behind, i bind the data as follows.
List<MyPoco> pocos = GetMyPocosFromSomewhere();
panorama.ItemsSource = myList;
ApplicationBar.IsVisible = true; //i only want to make this visible after the Panorama and ListBox controls have finished binding and rendering
right now, the code as i have sketched out above works, but the ApplicationBar is always visible before the Panorama/ListBox controls have rendered. to me, this makes the user experience awkward.
any help is appreciated.
Short answer would be "no, you can't detect it".
But a good solution is to add the command to the UI work queue aka. the Dispatcher. Like this:
Dispatcher.BeginInvoke(() => ApplicationBar.IsVisible = true);
That way, it'll first render it, when all the other UI tasks are done, and the experience shouldn't be so awkward.

Resources