RefreshView won't stop refreshing - Xamarin Forms - c#

For my Xamarin Forms application I am working on developing a calendar page. I have a No-SQL database set up and working and I have my main calendar view page (EventManagerPage) and the backend of this page (EventManagerViewPage).
The issue I am facing is how to let the system know that the RefreshView is refreshing?
The normal Xamarin.Forms.IsBusy indicator has not been working and my page will continue to refresh without stopping. I can tell the calendar events are there as they appear below the calendar after I pull down the page to refresh and click on a date, but the calendar itself does not show the events present (blue square per event on date).
My ViewModel is based off of a custom calendar ViewModel that extends both the INotifyPropertyChanged interface and the BaseViewModel.
Using IsBusy does not result in any error being thrown nor any messages in the debug output. I've tried some other ways around this to try and get the reload to stop once done but those have all resulted in errors that prevent the app from compiling.
So far I have tried creating a custom Boolean to act as the IsBusy Xamarin.Forms indicator but this resulted in the error of the member not being found in the data context.
I additionally attempted to follow the example in this Microsoft Doc on RefreshViews. This also resulted in the error of the member not being found in the data context and I was unable to set refreshView.Command = refreshCommand; (I'm not sure if this is important for the error).
I have put my code below. As a note, the calendar I am using is the Plugin.XCalendar by author MarvinE.
I appreciate any help/suggestions anyone has to offer!
CalendarBaseViewModel.cs
using MvvmHelpers;
using PropertyChanged;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace PPA.ViewModels
{
[AddINotifyPropertyChangedInterface]
public abstract class CalendarBaseViewModel : BaseViewModel, INotifyPropertyChanged
{
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Methods
protected virtual void OnPropertyChanged([CallerMemberName] string PropertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
#endregion
}
}
EventManagerViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Windows.Input;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
using PPA.Models;
using System.Threading.Tasks;
using PPA.Views;
using PPA.Services;
using System.Diagnostics;
namespace PPA.ViewModels
{
public class EventManagerViewModel : CalendarBaseViewModel
{
#region Properties
public ObservableRangeCollection<Event> Events { get; }
public ObservableRangeCollection<DateTime> SelectedDates { get; }
public ObservableRangeCollection<Event> SelectedEvents { get; }
#endregion
public AsyncCommand AddEventCommand { get; }
public AsyncCommand LoadEventsCommand { get; }
IEventDataStore EventService;
#region Constructors
public EventManagerViewModel()
{
AddEventCommand = new AsyncCommand(OnAddEvent);
LoadEventsCommand = new AsyncCommand(LoadEvents);
EventService = DependencyService.Get<IEventDataStore>();
Events = new ObservableRangeCollection<Event>();
SelectedDates = new ObservableRangeCollection<DateTime>();
SelectedEvents = new ObservableRangeCollection<Event>();
SelectedDates.CollectionChanged += SelectedDates_CollectionChanged;
}
#endregion
#region Methods
private void SelectedDates_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
SelectedEvents.ReplaceRange(Events.Where(x => SelectedDates.Any(y => x.DateTime.Date == y.Date)).OrderByDescending(x => x.DateTime));
}
private async Task OnAddEvent()
{
await Shell.Current.GoToAsync(nameof(NewEventPage));
}
async Task LoadEvents()
{
IsBusy = true;
// refreshview.IsRefreshing = true;
try
{
Events.Clear();
var events = await EventService.GetEventsAsync();
foreach (var ev in events)
{
Events.Add(ev);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
IsBusy = false;
}
public void OnAppearing()
{
IsBusy = true;
}
#endregion
}
}
EventManagerPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="PPA.Views.EventManagerPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:Converters="clr-namespace:PPA.Converters"
xmlns:Models="clr-namespace:PPA.Models"
xmlns:ViewModels="clr-namespace:PPA.ViewModels"
xmlns:xc="clr-namespace:XCalendar;assembly=XCalendar"
xmlns:xcModels="clr-namespace:XCalendar.Models;assembly=XCalendar"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit" xmlns:xcConverters="clr-namespace:XCalendar.Converters;assembly=XCalendar"
x:DataType="ViewModels:EventManagerViewModel"
x:Name="This"
Title="Event Calendar"
xct:SafeAreaEffect.SafeArea="True"
>
<ContentPage.Resources>
<!-- Limits a string to a certain amount of characters -->
<xcConverters:StringCharLimitConverter x:Key="StringCharLimitConverter"/>
<!-- Returns true if all bindings evaluate to true -->
<xct:VariableMultiValueConverter x:Key="AllTrueConverter" ConditionType="All"/>
<!-- Inverts a binded boolean value -->
<xct:InvertedBoolConverter x:Key="InvertedBoolConverter"/>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddEventCommand}" />
</ContentPage.ToolbarItems>
<RefreshView x:DataType="ViewModels:EventManagerViewModel" Command="{Binding LoadEventsCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
<Grid
ColumnSpacing="0"
RowDefinitions="Auto,*"
RowSpacing="0">
<Frame
Margin="10"
Padding="0"
BackgroundColor="White"
CornerRadius="15">
<xc:CalendarView
x:Name="MainCalendarView"
Grid.Row="0"
DayNameTextColor="{StaticResource ContentTextColor}"
NavigationArrowColor="{StaticResource ContentTextColor}"
NavigationBackgroundColor="Transparent"
NavigationTextColor="{StaticResource ContentTextColor}"
SelectedDates="{Binding SelectedDates}"
SelectionAction="Modify"
SelectionType="Single">
<xc:CalendarView.DayTemplate>
<DataTemplate x:DataType="{x:Type xcModels:CalendarDay}">
<!-- ContentView so that the margin is respected by the MonthView -->
<ContentView>
<xc:CalendarDayView
Margin="2.5"
HeightRequest="43"
CalendarView="{Binding ., Source={x:Reference MainCalendarView}}"
CurrentMonthTextColor="{StaticResource CalendarBackgroundTextColor}"
DateTime="{Binding DateTime}"
OutOfRangeTextColor="{StaticResource CalendarTertiaryColor}"
SelectedTextColor="{StaticResource CalendarPrimaryTextColor}"
TodayBorderColor="{StaticResource CalendarPrimaryColor}"
TodayTextColor="{StaticResource CalendarBackgroundTextColor}">
<xc:CalendarDayView.ControlTemplate>
<ControlTemplate>
<!-- Using a Grid to stack views on the z axis -->
<Grid RowSpacing="2">
<Grid.RowDefinitions>
<RowDefinition Height="1.5*"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- ContentPresenter displays the default content for the control -->
<ContentPresenter
Grid.Row="0"
Grid.RowSpan="2"
VerticalOptions="Center"/>
<StackLayout
Grid.Row="1"
HorizontalOptions="Center"
Orientation="Horizontal"
Spacing="2.5">
<!-- I want the event indicators to only be visible when the DateTime is in the currently navigated month -->
<StackLayout.IsVisible>
<MultiBinding Converter="{StaticResource AllTrueConverter}">
<!-- TemplatedParent refers to the view that the ControlTemplate resides in -->
<Binding Path="IsCurrentMonth" Source="{RelativeSource TemplatedParent}"/>
<Binding
Converter="{StaticResource InvertedBoolConverter}"
Path="IsOutOfRange"
Source="{RelativeSource TemplatedParent}"/>
</MultiBinding>
</StackLayout.IsVisible>
<BindableLayout.ItemsSource>
<Binding Path="DateTime.Date" Source="{RelativeSource TemplatedParent}">
<Binding.Converter>
<Converters:EventWhereConverter
Items="{Binding BindingContext.Events, Source={x:Reference This}}"
UseTimeComponent="False"
WhiteList="True"/>
</Binding.Converter>
</Binding>
</BindableLayout.ItemsSource>
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="{x:Type Models:Event}">
<BoxView
CornerRadius="100"
HeightRequest="7"
HorizontalOptions="CenterAndExpand"
VerticalOptions="Center"
WidthRequest="7"
Color="Blue"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</Grid>
</ControlTemplate>
</xc:CalendarDayView.ControlTemplate>
</xc:CalendarDayView>
</ContentView>
</DataTemplate>
</xc:CalendarView.DayTemplate>
</xc:CalendarView>
</Frame>
<CollectionView Grid.Row="1" ItemsSource="{Binding SelectedEvents}">
<CollectionView.EmptyView>
<Label
FontAttributes="Bold"
FontSize="20"
HorizontalTextAlignment="Center"
Text="No Events on Selected Date(s)"
TextColor="{StaticResource ContentTextColor}"
VerticalTextAlignment="Center"/>
</CollectionView.EmptyView>
<CollectionView.ItemsLayout>
<LinearItemsLayout ItemSpacing="0" Orientation="Vertical"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="{x:Type Models:Event}">
<ContentView Padding="5">
<Frame
Padding="0"
BackgroundColor="{StaticResource ContentBackgroundColor}"
CornerRadius="10">
<StackLayout Orientation="Horizontal" Spacing="0">
<BoxView BackgroundColor="CornflowerBlue" WidthRequest="20"/>
<StackLayout Padding="10" Spacing="0">
<Label
FontAttributes="Bold"
FontSize="20"
Text="{Binding DateTime, StringFormat='{0: dd MMMM HH:mm}'}"
TextColor="{StaticResource ContentTextColor}"
VerticalTextAlignment="Center"/>
<Label
FontSize="16"
Text="{Binding Title}"
TextColor="{StaticResource ContentTextColor}"
Margin="5,0,0,0"/>
<Label
Margin="5,10,0,0"
FontSize="14"
Text="{Binding Description}"
TextColor="{StaticResource ContentTextColor}"/>
</StackLayout>
</StackLayout>
</Frame>
</ContentView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</RefreshView>
</ContentPage>
EventManagerPage.xaml.cs
using PPA.ViewModels;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace PPA.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class EventManagerPage : ContentPage
{
EventManagerViewModel _viewModel;
public EventManagerPage()
{
InitializeComponent();
BindingContext = _viewModel = new EventManagerViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
_viewModel.OnAppearing();
}
}
}

Have you created binding property for "IsBusy" in the ViewModel as below:
private bool _isBusy;
public bool IsBusy
{
get
{
return _isBusy;
}
set
{
_isBusy = value;
OnPropertyChanged("IsBusy");
}
}

Related

Can't set up binding | MAUI

I'm developing a page that has a Collection View. Inside CollectionView.Footer, when I click on the button, I need to bind to Some Command, but I can't do it, because now there is a binding to SomeModel. Please tell me how to set up binding to ViewModel in CollectionView.Footer
.xaml:
<?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:viewmodels="clr-namespace:Paraglider.MobileApp.ViewModels"
xmlns:models="clr-namespace:Paraglider.MobileApp.Models"
x:Class="Paraglider.MobileApp.Pages.SomePage"
x:DataType="viewmodels:SomePageViewModel">
<Grid>
...
<CollectionView
Grid.Row="2" Grid.ColumnSpan="2"
ItemsSource="{Binding Items}"
SelectionMode="None">
<CollectionView.ItemsLayout>
...
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:SomeModel">
...
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.Footer>
<Border
Margin="10, 0"
Padding="10"
Stroke="Transparent"
StrokeShape="RoundRectangle 15,15,15,15">
<Border.Shadow>
<Shadow Brush="Black" Opacity="0.1" Radius="5" />
</Border.Shadow>
<StackLayout Orientation="Horizontal">
...
<Button
Padding="0"
CornerRadius="10"
HeightRequest="35" WidthRequest="60"
BackgroundColor="#FF8787"
FontFamily="geometria_medium" FontSize="24"
HorizontalOptions="End"
Text="+"
Command="{Binding SomeCommand}"/>
</StackLayout>
</Border>
</CollectionView.Footer>
</CollectionView>
</Grid>
</ContentPage>
.xaml.cs:
using Paraglider.MobileApp.ViewModels;
namespace Paraglider.MobileApp.Pages;
public partial class SomePage : ContentPage
{
public SomePage (SomePageViewModel viewModel)
{
BindingContext = viewModel;
InitializeComponent();
}
}
viewmodel.cs:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Paraglider.MobileApp.Infrastructure.Abstractions;
using Paraglider.MobileApp.Models;
using Paraglider.MobileApp.Services;
using System.Collections.ObjectModel;
namespace Paraglider.MobileApp.ViewModels;
public partial class SomePageViewModel : BaseViewModel
{
#region services
private readonly SomeService someService;
#endregion
#region fields
[ObservableProperty]
private ObservableCollection<SomeModel> someModels;
#endregion
public SomePageViewModel(SomeService someService)
{
this.someService= someService;
InitAsync();
}
public override async void InitAsync()
{
var someModels = await someService.GetAsync() ?? new();
SomeModels = new ObservableCollection<SomeModels>(components);
}
[RelayCommand]
private async Task SomeAsync()
{
...
}
}
To make sure that you can bind correctly to SomeCommand, you can use a RelativeSource for the binding and refer to the SomePageViewModel:
<Button Command="{Binding SomeCommand, Source={RelativeSource AncestorType={x:Type viewmodels:SomePageViewModel}}}" />
You can also browse the documentation on relative bindings and compiled bindings.

Xamarin.Forms - Binding a command

I am using ToolKits Expander and I am trying to bind a command, this is what I got so far:
public partial class AssignTaskPage : ContentPage
{
public AssignTaskPage()
{
InitializeComponent();
GetMathSubCatgories = new Command(() => MathSubCatgoriesCommand());
}
public ICommand GetMathSubCatgories { get; private set; }
void MathSubCatgoriesCommand()
{
Console.Write("Here");
}
}
And in my view
<xct:Expander Command="{Binding GetMathSubCatgories}">
<xct:Expander.Header>
<Frame Padding="10" Margin="10" HasShadow="False" BorderColor="LightGray" VerticalOptions="CenterAndExpand">
<StackLayout Orientation="Horizontal">
<Image Source="{Binding icon}" WidthRequest="25" HeightRequest="25"></Image>
<Label Text="{Binding name}" TextColor="{Binding textColor}" FontSize="Large" FontAttributes="Bold" HeightRequest="35" VerticalOptions="CenterAndExpand"></Label>
</StackLayout>
</Frame>
</xct:Expander.Header>
<Grid Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ListView x:Name="SubCategories" ItemsSource="{Binding subCategories}" ItemSelected="SubCategories_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding name}" TextColor="#02cc9d" FontAttributes="Bold" HeightRequest="35" VerticalOptions="CenterAndExpand"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</xct:Expander>
This does not work at all, (I put a break point on Console.Write("Here"); and its not hitting it)
So I did some digging and found this tutorial:
https://www.syncfusion.com/kb/12154/how-to-bind-command-to-expander-in-itemtemplate-of-xamarin-forms-listview-sflistview
and here is the sample in git.
https://github.com/SyncfusionExamples/command-to-expander-in-itemtemplate-listview-xamarin
I understand what I have to do here, the problem I am facing is when this Command is called, I was looking to get a value and use it in my AssignTaskPage, but what the tutorial is saying to have a ViewModel which is in a separate file. So should I setup a MessagingCenter in my AssignTaskPage and call it in the ViewModel to get the value I want and pass it to AssignTaskPage?
Because your command isn't defined in the ViewModel that you're binding to.You could bind the command which is defined in your AssignTaskPage,and then bind the viewmodel for the parent element of the expander.
For example :
public partial class AssignTaskPage : ContentPage
{
public AssignTaskPage()
{
InitializeComponent();
GetMathSubCatgories = new Command(() => MathSubCatgoriesCommand());
BindingContext = this;
}
public ICommand GetMathSubCatgories { get; private set; }
void MathSubCatgoriesCommand(object obj)
{
DisplayAlert("Alert!", "" + (obj as Contact).ContactName + "expanded", "Ok");
}
}
the xaml (here use the xaml codes of the above sample),the grid bind the viewmodel,and your expander bind the command of the root (your page):
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ExpanderXamarin"
x:Class="ExpanderXamarin.ExpandableListView"
x:Name="root"
xmlns:sflistview="clr-namespace:Syncfusion.ListView.XForms;assembly=Syncfusion.SfListView.XForms"
xmlns:expander="clr-namespace:Syncfusion.XForms.Expander;assembly=Syncfusion.Expander.XForms">
<ContentPage.Content>
<Grid x:Name="mainGrid" BackgroundColor="#F0F0F0" Padding="4">
<Grid.BindingContext>
<local:ViewModel />
</Grid.BindingContext>
<sflistview:SfListView x:Name="listView" AutoFitMode="DynamicHeight" ItemsSource="{Binding ContactsInfo}">
<sflistview:SfListView.ItemTemplate>
<DataTemplate>
<Frame x:Name="frame" CornerRadius="2" Padding="{OnPlatform Android=1, iOS=1, UWP=0}" Margin="{OnPlatform Android=1, iOS=1, UWP=0}" OutlineColor="White" HasShadow="{OnPlatform Android=true, iOS=false, UWP=true}">
<Grid Padding="{OnPlatform Android=2, iOS=2, UWP=0}" Margin="{OnPlatform Android=1, iOS=1, UWP=0}" BackgroundColor="White" >
<expander:SfExpander x:Name="expander" HeaderIconPosition="None">
<expander:SfExpander.Behaviors>
<local:EventToCommandBehavior Command="{Binding Path=BindingContext.GetMathSubCatgories, Source={x:Reference root}}" EventName="Expanding" CommandParameter="{Binding .}"/>
</expander:SfExpander.Behaviors>
<expander:SfExpander.Header>
...
</expander:SfExpander.Header>
<expander:SfExpander.Content>
..
</expander:SfExpander.Content>
</expander:SfExpander>
</Grid>
</Frame>
</DataTemplate>
</sflistview:SfListView.ItemTemplate>
</sflistview:SfListView>
</Grid>
</ContentPage.Content>
</ContentPage>
if you want get the parameters you could bind the CommandParameter like above.

Problem with Xamarin app while calling an API

I'm a beginner with C# and whole .NET (just wanted to mention at the beginning). So I'm learning Xamarin and APIs.
I've got the problem with fetching some data from API (https://jsonplaceholder.typicode.com/posts) to display inside the app. Once I execute the app, it gives me an error:
System.InvalidCastException: 'Specified cast is not valid'.
I also don't see where is it failing (after error, exception window is blank - like not showing where it stops).
using ExerciseApp.Models;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net.Http;
using Xamarin.Forms;
namespace ExerciseApp
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
GetPosts();
}
private async void GetPosts()
{
HttpClient client = new HttpClient();
var response = await client.GetStringAsync("https://jsonplaceholder.typicode.com/posts");
var posts = JsonConvert.DeserializeObject<List<Posts>>(response);
PostsListView.ItemsSource = posts;
}
}
}
<?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="ExerciseApp.MainPage">
<StackLayout BackgroundColor="White">
<Frame BackgroundColor="#581845" Padding="10" CornerRadius="5" Margin="25">
<Label Text="ʕ•ᴥ•ʔ" HorizontalTextAlignment="Center" TextColor="White" FontSize="30"/>
</Frame>
<Label Text="Welcome to Exercise app with RESTAPI and NHibernate" TextColor="#581845" FontSize="Title" Padding="10" HorizontalTextAlignment="Center"/>
<Label FontSize="20" Padding="20" HorizontalTextAlignment="Center" TextColor="#581845">
<Label.FormattedText>
<FormattedString>
<FormattedString.Spans>
<Span Text="Here we will load some data"/>
</FormattedString.Spans>
</FormattedString>
</Label.FormattedText>
</Label>
<ListView x:Name="PostsListView">
<ListView.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding id}" TextColor="#581845"></Label>
<Label Text="{Binding title}" TextColor="#581845"></Label>
</StackLayout>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
namespace ExerciseApp.Models
{
public class Posts
{
public int userId { get; set; }
public int id { get; set; }
public string title { get; set; }
public string body { get; set; }
}
}
ListView must use a Cell type in their template. Adding a ViewCell into your XAML will fix this problem
<ListView x:Name="PostsListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding id}" TextColor="#581845"></Label>
<Label Text="{Binding title}" TextColor="#581845"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
alternately, you can use a CollectionView which allows you to create templates without the restriction of using a Cell

Xamarin C# button reference doesnt exist in current context

Im using Xamarin to develop an application
Basically an admin user should be able to have access to this button. I have used this logic previously and has worked fine.
A regular user should not have access to hide other regular users reviews.
I was told there is issue with it being nested within a carousel view.
I could restructure the whole thing but i like the design currently being used
The XAML code is as follows:
<?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="GOV.ReviewPage" Title="Reviews" >
<Grid RowDefinitions="20,180,20,32,230,90" ColumnDefinitions="20,*,20">
<Frame Grid.Row="1" Grid.Column="1" Grid.RowSpan="2">
<CarouselView x:Name="MainCarousel" IndicatorView="indicatorView" IsBounceEnabled="True" >
<CarouselView.ItemTemplate>
<DataTemplate>
<ScrollView>
<Grid RowDefinitions="20,20,*" ColumnDefinitions="75,*" Margin="15,0,25,0">
<Label Text="Username:" Grid.Row="0" Grid.Column="0" HorizontalTextAlignment="Start" />
<Label Text="{Binding User.Username}" FontSize="15" Grid.Row="0" Grid.Column="1" HorizontalTextAlignment="Start" />
<Label Text="Description:" Grid.Row="1" Grid.Column="0" HorizontalTextAlignment="Start" />
<Label Text="{Binding Description}" FontSize="15" HorizontalTextAlignment="Start" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"/>
<Frame Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" >
<Button x:Name="HideButtonRef" Clicked="HideButton" Text="{Binding stringVal}"
HeightRequest="50" />
</Frame>
</Grid>
</ScrollView>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
</Frame>
</Grid>
</ContentPage>
and The problematic C# logic:
protected override async void OnAppearing()
{
if (User.Admin) { HideButtonRef.IsEnabled = false; } // this doesnt work for some reason
else {HideButtonRef.IsEnabled = true; }
base.OnAppearing();
await LoadList();
}
The c# logic says " the name 'HideButtonRef' does not exist in the current context"
Create a property on the codebehind to bind to. You have to make sure the ViewModel implements INotifyPropertyChanged:
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Then create a property that holds the value of whether or not to show the button.
bool _isAdmin;
public bool IsAdmin
{
get {return _isAdmin;}
set
{
_isAdmin = value;
OnPropertyChanged("IsAdmin");
}
}
Then change the button definition in the XAML to:
<Button x:Name="HideButtonRef"
Clicked="HideButton"
Text="{Binding stringVal}"
IsVisible="{Binding IsAdmin}"
HeightRequest="50" />
And the OnAppearing now becomes
protected override async void OnAppearing()
{
IsAdmin = !User.Admin;
base.OnAppearing();
await LoadList();
}

Xamarin.Forms: ListView not Displaying in Android

Good Day. I'm creating a simple Xamarin.Forms (Portable) application that allows me to create record of an Employee and saved it on a Database in Visual Studio. All records are being displayed to a ListView.
My application is doing fine when I run it on UWP. It displays the all the created records on a ListView properly.
But when I run it on Android Platform, it does not display any record in the ListView. I even tried to check on the Web API whether it returns a value or not. And I did get a value. Meaning, the problem is on Android Platform why it isn't displaying any record.
Have you encountered this problem? What do you think is the reason behind this? What can I do now? Sorry I'm just a newbie here in Xamarin. Hope you can help me. Thanks a lot.
These are some codes I have:
EmployeeRecordsPage.xaml
<?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="XamarinFormsDemo.EmployeeRecordsPage"
xmlns:ViewModels="clr-namespace:XamarinFormsDemo.ViewModels;assembly=XamarinFormsDemo"
xmlns:controls="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin.Abstractions"
BackgroundImage="bg3.jpg"
Title="List of Employees">
<ContentPage.BindingContext>
<ViewModels:MainViewModel/>
</ContentPage.BindingContext>
<StackLayout Orientation="Vertical">
<ListView ItemsSource="{Binding EmployeesList}"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="10" RowSpacing="10" ColumnSpacing="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<controls:CircleImage Source="icon.png"
HeightRequest="66"
HorizontalOptions="CenterAndExpand"
Aspect="AspectFill"
WidthRequest="66"
Grid.RowSpan="2"
/>
<Label Grid.Column="1"
Text="{Binding EMPLOYEE_NAME}"
TextColor="#24e97d"
FontSize="24"/>
<Label Grid.Column="1"
Grid.Row="1"
Text="{Binding EMP_NUMBER}"
TextColor="White"
FontSize="18"
Opacity="0.6"/>
<Label Grid.Column="1"
Grid.Row="2"
Text="{Binding DEPT_COMP}"
TextColor="White"
FontSize="18"
Opacity="0.6"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackLayout Orientation="Vertical"
Padding="30,10,30,10"
HeightRequest="20"
BackgroundColor="#24e97d"
VerticalOptions="Center"
Opacity="0.5">
<Label Text="© Copyright 2016 SMESOFT.COM.PH All Rights Reserved "
HorizontalTextAlignment="Center"
VerticalOptions="Center"
HorizontalOptions="Center" />
</StackLayout>
</StackLayout>
</ContentPage>
..
EmployeeViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using XamarinFormsDemo.Models;
using XamarinFormsDemo.Services;
namespace XamarinFormsDemo.ViewModels
{
public class MainViewModel : INotifyPropertyChanged
{
private List<Employee> _employeesList;
private Employee _selectedEmployee = new Employee();
public List<Employee> EmployeesList
{
get { return _employeesList; }
set
{
_employeesList = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
InitializeDataAsync();
}
private async Task InitializeDataAsync()
{
var employeesServices = new EmployeesServices();
EmployeesList = await employeesServices.GetEmployeesAsync();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
..
EmployeesServices.cs
using Plugin.RestClient;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using XamarinFormsDemo.Models;
namespace XamarinFormsDemo.Services
{
public class EmployeesServices
{
public async Task<List<Employee>> GetEmployeesAsync()
{
RestClient<Employee> restClient = new RestClient<Employee>();
var employeesList = await restClient.GetAsync();
return employeesList;
}
}
}
I think the issue is that you are not updating the ListView after you get your items with:
EmployeesList = await employeesServices.GetEmployeesAsync();
I would suggest using an ObservableCollection instead of a List. With an observable collection the ListView should get updated automatically when items in the ObservableCollection are added or deleted. So instead of:
private List<Employee> _employeesList;
try:
private ObservableCollection<Employee> _employeesList;
or you can just assign the ListView.ItemsSource to null and then back to the List<Employee> after you get your data:
EDIT: As noted by Jaycee, the following code to reset the ItemsSource should not be in the view model as the view model won't have access to listView. However the way to refresh the listView.ItemsSource is correct. The view model just needs to let the view know that the EmployeesList was updated and then you can reset the listView.ItemsSource in the code behind for the view. This could be done in a couple of ways I can think of. For instance you could have a delegate in the ViewModel that the view can provide the code implementation for or the view model could raise an event that the view can subscribe to. Basically you just have to let the view know that it needs to refresh its ListView's ItemsSource. But all of this could be avoided by using an ObservableCollection instead of a list as I noted previously.
EmployeesList = await employeesServices.GetEmployeesAsync();
listView.ItemsSource = null;
listView.ItemsSource = EmployeesList;
And to do the above you would have to give your ListView a name that you can use to reference it in code. YOu can set this name in the XAML for the ListView:
<ListView
ItemsSource="{Binding EmployeesList}"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
x:Name="listView"
>
But the ObservableCollection would be the preferable option.

Categories

Resources