Xamarin.Forms block UI when ItemsSource load a big data - visual-studio

I do not load a small data from the API, in C# code, they are loaded in advance and everything seems to be fine, but as soon as I open the page where ItemsSource = "{Binding BigData}", my UI is blocked for 10 seconds.
Are there any ideas to open the page first, then start loading data without blocking the UI?

I would to suggest you can kick off a task in your view models constructor that loads the data. Using Async and await to load bid data.
I do one sample that using ListView to display 100000 records.
<StackLayout>
<Label Text="test ui in xamarin.forms asyn" />
<ActivityIndicator IsRunning="{Binding isBusy}" IsVisible="{Binding isBusy}" />
<ListView x:Name="listview1" ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding name}" />
<Label HorizontalOptions="CenterAndExpand" Text="{Binding age}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
public partial class Page19 : ContentPage
{
public Page19()
{
InitializeComponent();
this.BindingContext = new ItemsViewModel();
}
}
public class ItemsViewModel:ViewModelBase
{
private bool _isBusy;
public bool isBusy
{
get { return _isBusy; }
set
{
_isBusy = value;
RaisePropertyChanged("isBusy");
}
}
public ObservableCollection<people> Items { get; set; }
public ItemsViewModel()
{
Items = new ObservableCollection<people>();
isBusy = true;
Task.Run(async () => await LoadItems());
}
public async Task LoadItems()
{
var items = new ObservableCollection<people>(); // new collection
if (isBusy)
{
await Task.Delay(10000);
// var loadedItems = ItemsService.LoadItemsDirectory();
//foreach (var item in loadedItems)
// items.Add(item);
for (int i = 0; i < 100000; i++)
{
people p = new people();
p.name = "people " + i;
p.age = i;
items.Add(p); // items are added to the new collection
}
Items = items; // swap the collection for the new one
RaisePropertyChanged(nameof(Items)); // raise a property change in whatever way is right for your VM
isBusy = false;
}
}
}
public class people
{
public string name { get; set; }
public int age { get; set; }
}
ViewModelBase is one class that implementing INotifyPropertyChanged
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Related

CollectionView dose not show content, when filled in onappearing

I have a collectionview that is bound to an ObservableRangeCollectionin my ViewModel.
In my ViewModel there is a Method that runs onAppearing and I want my ColletionViewto be filled from there, but when I do so the collectionveiw dose not display the content only when i reload the content is shown.
View:
<RefreshView Grid.Row="1"
Grid.RowSpan="2"
Command="{Binding RefreshCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<RefreshView.RefreshColor>
<OnPlatform x:TypeArguments="Color">
<On Platform="iOS" Value="White"/>
</OnPlatform>
</RefreshView.RefreshColor>
<CollectionView x:Name="Collection"
ItemsSource="{Binding Locations, Mode=OneWay}"
ItemTemplate="{StaticResource ListDataTemplate}"
RemainingItemsThresholdReachedCommand="{Binding LoadMoreCommand}"
RemainingItemsThreshold="10"
SelectionMode="Single"
BackgroundColor="Transparent"
ItemsLayout="VerticalList"
SelectedItem="{Binding SelectedItem}"
SelectionChangedCommand="{Binding SelectedCommand}">
<CollectionView.EmptyView>
<StackLayout Padding="12">
<Label HorizontalOptions="Center" Text="Keine Daten vorhanden!" TextColor="White"/>
</StackLayout>
</CollectionView.EmptyView>
</CollectionView>
</RefreshView>
ViewModel:
namespace YourPartys.ViewModels
{
public class ListViewModel : ViewModelBase
{
#region Variables
#endregion
#region Propertys
LocationModel selectedItem;
public LocationModel SelectedItem
{
get => selectedItem;
set => SetProperty(ref selectedItem, value);
}
public ObservableRangeCollection<LocationModel> Locations { get;set; } = new ObservableRangeCollection<LocationModel>();
double distance;
public double Distance
{
get => distance;
set => SetProperty(ref distance, value);
}
#endregion
#region Commands
public ICommand FilterButtonCommand { get; }
public ICommand RefreshCommand { get; }
public ICommand SelectedCommand { get; }
public ICommand LoadMoreCommand { get; }
#endregion
//Constructor
public ListViewModel()
{
FilterButtonCommand = new Command(OpenFilter);
RefreshCommand = new AsyncCommand(Refresh);
SelectedCommand = new AsyncCommand(Select);
}
public override async void VModelActive(Page sender, EventArgs eventArgs)
{
base.VModelActive(sender, eventArgs);
var locs = await FirestoreService.GetLocations("Locations");
Locations.AddRange(locs);
}
private void OpenFilter(object obj)
{
PopupNavigation.Instance.PushAsync(new ListFilterPage());
}
private async Task Refresh()
{
IsBusy = true;
var locs = await FirestoreService.GetLocations("Locations");
Locations.AddRange(locs);
IsBusy = false;
}
private async Task Select()
{
if (SelectedItem == null)
return;
var route = $"{nameof(DetailPage)}?Locationid={SelectedItem.Locationid}";
SelectedItem = null;
await AppShell.Current.GoToAsync(route);
}
}
}
There are several problems in your demo.
1.Since you set the BindingContext for your page in xaml as follows:
<ContentPage.BindingContext>
<viewmodels:MainViewModel/>
</ContentPage.BindingContext>
you didn't need to recreate another object MainViewModel in a CS file and reference it. These are two different objects.
MainViewModel viewModel;
viewModel = new MainViewModel();
protected override void OnAppearing()
{
base.OnAppearing();
viewModel.VModelActive(this, EventArgs.Empty);
}
So, you can get the BindingContext in MainPage.xaml.cs in function OnAppearing as follows:
protected override void OnAppearing()
{
base.OnAppearing();
viewModel = (MainViewModel)this.BindingContext;
viewModel.VModelActive(this, EventArgs.Empty);
}
The whole code is
public partial class MainPage : ContentPage
{
MainViewModel viewModel;
public MainPage()
{
InitializeComponent();
// viewModel = new MainViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
viewModel = (MainViewModel)this.BindingContext;
viewModel.VModelActive(this, EventArgs.Empty);
}
}
2.when we set the text color of the Label to White,this makes it hard to see the text,so you can reset it to another color,for example Black:
<Label Text="{Binding Name}"
FontSize="30"
TextColor="White"/>

How to mark the checkbox in repeater in Xamarin.Forms?

I am using checkbox control under repeater to do a radio button functionality, everything seems to be fine but now stuck on how to bind the checkbox when the page loads. I have saved the radio button text whichever was selected and once user come back to page again I want to bin what he has selected last time. Not getting any hint here how to proceed.
<grial:Repeater
x:Name="PP"
SelectionMode="Single"
InitialSelection="Empty"
ItemSize="100"
HorizontalOptions="Start"
ItemsSource="{Binding BlowerPostions}">
<grial:Repeater.ItemTemplate>
<DataTemplate>
<grial:Checkbox
IsChecked="false"
UncheckedBorderColor="Black">
<Label
TextColor="Black"
Text="{ Binding . }"
Margin="8,0" />
</grial:Checkbox>
</DataTemplate>
</grial:Repeater.ItemTemplate>
<grial:Repeater.SelectedItemTemplate>
<DataTemplate>
<grial:Checkbox
IsChecked="true"
UncheckedBorderColor="Black"
InputTransparent="true">
<Label
TextColor="Black"
Text="{ Binding . }"
Margin="8,0" />
</grial:Checkbox>
</DataTemplate>
</grial:Repeater.SelectedItemTemplate>
</grial:Repeater>
View Model :
public class ProductionViewModel : INotifyPropertyChanged
{
public ObservableCollection<BlowerPostion> _blowerPostions;
public ObservableCollection<BlowerPostion> BlowerPostions
{
get => _blowerPostions;
set
{
_blowerPostions = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs("BlowerPostions"));
}
}
}
public void LoadData()
{
BlowerPostions = new ObservableCollection<BlowerPostion>();
BlowerPostions.Add(new BlowerPostion("Left", 1));
BlowerPostions.Add(new BlowerPostion("Standard", 1));
}
}
public class BlowerPostion
{
public string Text { get; set; }
public int Id { get; set; }
public BlowerPostion(string _text, int _id)
{
Text = _text;
Id = _id;
}
}
I don't use grial:Repeater,but you can refer to the following code which use CheckBox in ListView item.
Item.cs
public class Item
{
public string Name { get; set; }
public string Type { get; set; }
public string Image { get; set; }
//This field indicates whether or not it is selected
public bool isChecked { get; set; }
}
MyViewModel.cs
public class MyViewModel
{
public ObservableCollection<Item> items { get; private set; }
public MyViewModel() {
items = new ObservableCollection<Item>();
items.Add(new Item { Name = "Tomato", Type = "Fruit", Image = "tomato.png", isChecked = true });
items.Add(new Item { Name = "Romaine Lettuce", Type = "Vegetable", Image = "lettuce.png", isChecked = false });
items.Add(new Item { Name = "Zucchini", Type = "Vegetable", Image = "zucchini.png", isChecked = false });
}
}
TestPage1.xaml
<ContentPage.Content>
<ListView x:Name="listview" ItemsSource="{Binding items}" VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" Padding="5,0,5,0">
<Label Text="{Binding Name}" HorizontalOptions="StartAndExpand" FontSize="30"/>
<input:CheckBox IsChecked="{Binding isChecked}" Type="Check" Color="White" BoxBackgroundColor="Green" TextColor="White" HeightRequest="40"
CheckChanged="CheckBox_CheckChanged" BindingContext="{Binding .}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
TestPage1.xaml.cs
public partial class TestPage1 : ContentPage
{
public List<Item> selectedItems; // define `selectedItems` as the list of selected items.
public MyViewModel viewModel;
public TestPage1 ()
{
InitializeComponent ();
selectedItems = new List<Item>(); // init the `selectedItems`
viewModel = new MyViewModel();
BindingContext = viewModel;
}
private void CheckBox_CheckChanged(object sender, EventArgs e)
{
var checkbox = (Plugin.InputKit.Shared.Controls.CheckBox)sender;
var ob = checkbox.BindingContext as Item;
if (ob != null)
{
System.Diagnostics.Debug.WriteLine("isChecked = " + ob.isChecked + "<---> Name = " + ob.Name +"<---> Type = " + ob.Type );
if (ob.isChecked)
{
selectedItems.Add(ob);
}
else {
// remove the item
}
}
}
}
Note:
1.add new field isChecked in item model
public bool isChecked { get; set; }
2.Add event CheckChanged for the item.And when we check the CheckBox,we can get the corresponding value isChecked of the CheckBox.
<input:CheckBox IsChecked="{Binding isChecked}" Type="Check" Color="White" BoxBackgroundColor="Green" TextColor="White" HeightRequest="40"
CheckChanged="CheckBox_CheckChanged" BindingContext="{Binding .}" />

Why doesn't CollectionView highlight SelectedItems within RefreshView

I'm getting two collections of data from a web service - the full collection and then a subset of items. The subset should be selected on load. I cannot get the SelectedItems to show as selected in the CollectionView, event though that property is set. I've looked over the Monkeys example and all over the web but can't find an example just like this
View:
<RefreshView IsRefreshing="{Binding IsBusy, Mode=TwoWay}" Command="{Binding LoadItemsCommand}">
<CollectionView x:Name="ExperiencesCollectionView"
ItemsSource="{Binding Experiences}"
SelectedItems="{Binding ProfileExperiences, Mode=TwoWay}"
SelectionMode="Multiple">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label
Text="{Binding Description}"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
</StackLayout>
Code Behind:
public partial class ExperiencesPage : ContentPage
{
ExperiencesViewModel viewModel;
public ExperiencesPage()
{
InitializeComponent();
BindingContext = viewModel = new ExperiencesViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
if (viewModel.Experiences.Count == 0)
viewModel.IsBusy = true;
}
ViewModel:
public class ExperiencesViewModel : BaseViewModel
{
public ObservableCollection<Experience> Experiences { get; set; }
public ObservableCollection<object> ProfileExperiences { get; set; }
public Command LoadItemsCommand { get; set; }
public string InterestedIn { get; set; }
public ExperiencesViewModel()
{
Title = "My Experiences";
Experiences = new ObservableCollection<Experience>();
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
}
async Task ExecuteLoadItemsCommand()
{
IsBusy = true;
try
{
Experiences.Clear();
//Get these from the REST service
var experiences = await App.ExperienceManager.GetExperiencesAsync();
var serviceExperiences = await App.ExperienceManager.GetProfileExperiencesAsync();
foreach (var exp in experiences)
{
Experiences.Add(exp);
}
foreach (var profileExp in serviceExperiences)
{
ProfileExperiences.Add(profileExp);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}

Data binding for listview in xamarin

I am trying to populate listview with database table in xamarin forms app
I am getting null pointer exception
Below is XAML for listview
<ListView x:Name="_listView"
ItemsSource="{Binding itemsInList}"
Grid.Column="0"
Grid.Row="0"
SelectedItem="{Binding SelectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}" Grid.Column="0" Grid.Row="0" />
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Below is xaml.cs(code behind)
public List <ServiceProvider> itemlist;
public List <ServiceProvider> itemsInList
{
get {return itemlist;}
}
protected override void OnAppearing()
{
base.OnAppearing();
ExpensesDatabase dbcon = new ExpensesDatabase(completePath);
itemlist = dbcon.GetItems(completePath);
// _listView.ItemsSource = itemlist;
}
Below is db file
public class ExpensesDatabase
{
readonly SQLiteConnection database;
public ExpensesDatabase(string dbPath)
{
database = new SQLiteConnection(dbPath);
database.CreateTable < ServiceProvider > ();
}
public List < ServiceProvider > GetItems(string dbPath)
{
return database.Table < ServiceProvider > ().ToList();
}
}
Data is not displayed in listview
If you want the ListView to automatically update as items are added, removed and changed in the underlying list, you'll need to use an ObservableCollection. ObservableCollection is defined in System.Collections.ObjectModel and is just like List, except that it can notify ListView of any changes:
public ObservableCollection<ServiceProvider> itemsInList { get; set; }
Then make sure you have set the right bindingContext and initialized the ObservableCollection:
public MainPage()
{
InitializeComponent();
itemsInList = new ObservableCollection<ServiceProvider>();
BindingContext = this;
}
I write a sample to test and it works on my side, you can have a look at the full code:
public partial class MainPage : ContentPage
{
public ObservableCollection<ServiceProvider> itemsInList { get; set; }
public MainPage()
{
InitializeComponent();
itemsInList = new ObservableCollection<ServiceProvider>();
BindingContext = this;
}
protected override void OnAppearing()
{
base.OnAppearing();
itemsInList.Add(new ServiceProvider() { Name= "a"});
}
}
public class ServiceProvider : INotifyPropertyChanged
{
string name;
public event PropertyChangedEventHandler PropertyChanged;
public ServiceProvider()
{
}
public String Name
{
set
{
if (name != value)
{
name = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
get
{
return name;
}
}
}
Feel free to ask me any question if you still can't solve it.

"Refresh" Pivot Control with Mvvm-light toolkit for WP7

I have in my Xaml a pivot control :
<controls:Pivot ItemsSource="{Binding ObjectList}">
<controls:Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</controls:Pivot.HeaderTemplate>
<controls:Pivot.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Value1}" />
<TextBlock Text="{Binding Value2}" />
</StackPanel>
</DataTemplate>
</controls:Pivot.ItemTemplate>
</controls:Pivot>
My ViewModel is :
public class MyObject
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
public class MyViewModel : ViewModelBase
{
public const string ObjectListPropertyName = "ObjectList";
private ObservableCollection<MyObject> _objectList;
public ObservableCollection<MyObject> ObjectList
{
get
{
return _objectList;
}
private set
{
if (_objectList == value)
return;
_objectList = value;
RaisePropertyChanged(ObjectListPropertyName);
}
}
private DispatcherTimer timer;
public MyViewModel()
{
ObservableCollection<MyObject> collection = new ObservableCollection<MyObject>
{
new MyObject {Value1 = "One"},
new MyObject {Value1 = "Two"},
new MyObject {Value1 = "Tree"}
};
ObjectList = collection;
timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2)};
timer.Tick += timer_Tick;
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
foreach (MyObject myObject in _objectList)
{
myObject.Value2 = "Something";
}
Application.Current.RootVisual.Dispatcher.BeginInvoke( () => RaisePropertyChanged(ObjectListPropertyName));
}
}
When the timer_tick is reached, I supposed the pivot control to refresh with the new values ... but I can't see any changes.
What do I miss ?
Thanks in advance for your help
I'm guessing that possibly updating the members of the list without updating the list itself is the problem. When you raise the property changed event - it is for the entire collection. The collection is still pointing to an equal reference of itself, despite the fact that the members have changed.
Try placing a breakpoint in the setter and see if the property changed event is fired.

Resources