Xamarin: Updating an ObservableCollection in the Background - xamarin

I have a Xamarin.Forms Application based on a Listview populated by an ObservableCollection<Item> and bound to a SQLite model exposed as List<Item>.
Now I have difficulties figuring out how I can update the data via the web.
The whole process is supposed to run in the background. If everything runs as desired, the update process would be based on async ... await tasks and new items would appear one by one on the screen as they are pulled in.
Would anyone please guide me how to lay out my application and how to implement such a background update task?
Notes:
More experienced colleagues warned me that such a concept cannot be done in Xamarin at all, since, so they say, ObservableCollection "does not support to be updated by a background task". I did some research on this, and found indeed some indication that this could be true, but the infos were long outdated (from 2008), things have very likely changed since then.
For performance reasons I cannot simply pull in the complete list and throw away the existing list, but I need to implement a record based update looking at the items one by one. To accomplish this records have an unique Id, and are timestamped. My app knows when it has last seen the web service, and can query all items which have changed since then. I already have a REST service pulling in the changed Items data from the backend as a List, but cannot figure out how to refresh the ObservableCollection and the underlying database.

I do all my updates on change to the ListView. Here I have a button in the a list view which when clicked updates a property which persists by saving it to the sql database. It assumes you have your database set up.
Database:
Function which updates item if exist or saves new. This is a Task so can be called asynchronously.
public Task<int> SaveItemAsync(Item item)
{
if(item.ItemId != 0)
{
return database.UpdateAsync(item);
}
else
{
return database.InsertAsync(itme);
}
}
Xaml
List View which binds to an Observable collection created from the item database.
GestureRecognizers is set up on the image and is bound to a tapCommand in the ViewModel - The code behind the Xaml defines the binding context
Code behind
ItemViewModel viewModel;
public MessagePage ()
{
InitializeComponent ();
viewModel = new ItemViewModel();
BindingContext = viewModel;
}
And then the Xaml
Bind to the ObsevableCollection "ItemsPassed" and this set as the binding context within it. As a result you need to go back to the
page BindingContext so note the binding path for the TapCommand.
Pass the ItemId through as a parameter
<ListView ItemsSource="{Binding ItemsPassed}"
HasUnevenRows="True"
x:Name="ItemListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView>
<StackLayout Padding="20">
<StackLayout Orientation="Horizontal">
<StackLayout Orientation="Vertical" HorizontalOptions="Start">
<Label Text="{Binding ItemText}"
FontAttributes="Bold"/>
</StackLayout>
<Image Source="{Binding Favourited, HeightRequest="12" HorizontalOptions="EndAndExpand">
<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Path=BindingContext.TapCommand, Source={x:Reference ItemListView}}"
CommandParameter="{Binding ItemId}"/>
</Image.GestureRecognizers>
</Image>
</StackLayout>
</StackLayout>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel
Define the ICommand and assign a function to it
Find the item in the observable collection and change the property
This also needs to be changed in the database using the await App.Database.SaveItemAsync(item) - because of this the function called of the command must be async
public class ItemsViewModel : INotifyPropertyChanged
{
public ObservableCollection<Item> ItemsPassed { get; set; } = new ObservableCollection<Item>();
// Get items out of yourdatabase
public async void GetItems()
{
List<Item> itemsOut = await App.Database.GetItemsAsync();
foreach (Item i in itemsOut)
{
ItemsPassed.Add(i);
}
}
ICommand tapCommand;
public ItemsViewModel()
{
GetItems();
tapCommand = new Command(ExecuteTap);
}
public ICommand TapCommand
{
get { return tapCommand; }
}
// Find the item selected and change the property
private async void ExecuteTap(object obj)
{
var item = ItemsPassed.FirstOrDefault(i => i.ItemId.ToString() == obj.ToString());
if (item != null)
{
if (item.Favourited == true)
{
item.Favourited = false;
}
else
{
item.Favourited = true;
}
await App.Database.SaveItemAsync(item);
Console.WriteLine("update the database");
}
}
}
You then want to make sure the changes occur in the view - this is done through extending INotifyPropertyChange in the Model and calling it when the property changes.
Model
When the item is changed in the viewmodel the OnPropertyChanged is fired which cause the view to update.
public class Item : INotifyPropertyChanged
{
[PrimaryKey, AutoIncrement]
public int ItemId { get; set; }
public string ItemText { get; set; }
public string Author { get; set; }
private bool _favourited;
public bool Favourited
{
get { return _favourited; }
set
{
_favourited = value;
OnPropertyChanged("Favourited");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Hope this helps. Sorry I had to put things in snippets the code section wasn't working properly probably because some of the code is wrong, but its just for example.

Related

GridView is not updating on UI after changing the item source list

I am using a GridView with item source property which is the list of objects. Initially I assign the list of items on page appearing which is also appearing on UI. But when i perform some operation on button click and update the items in the list, it doesn't updates on UI, I need to go back and come again.
<grial:GridView
x:Name="ItemList"
Padding="10"
ColumnSpacing="10"
RowSpacing="10"
ColumnCount="{
grial:OnOrientationInt
PortraitPhone=2,
LandscapePhone=2,
PortraitTablet=2,
LandscapeTablet=2,
PortraitDesktop=2,
LandscapeDesktop=2
}"
VerticalOptions="Start"
ItemsSource="{ Binding Items ,Mode=TwoWay}"
Style="{ StaticResource ResponsiveLandscapeMarginStyle }">
<grial:GridView.ItemTemplate>
<DataTemplate>
<local:RegisterItemGridV2 />
</DataTemplate>
</grial:GridView.ItemTemplate>
</grial:GridView>
My View model
public class LoadDistinctItemsViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private IList<Items> items;
public IList<Items> Items
{
get => items;
set
{
items = value;
NotifyPropertyChanged();
}
}
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void LoadData(string _loadId, int _mode = 0)
{
Items = App.dataManager.GetLoadDistinctItems(_loadId, _mode);
}
}
I am calling loadData which is updating everytime, the count in Items list is increasing but not updating on UI.
You can use ObservableCollection instead of IList . It works fine on my side .
public ObservableCollection<string> Items { get; set; }
Since you are modifiyng the List every often, you should not use List.
If you use a List and add or remove items at runtime, your ListView /Grid will not reflect those changes.
If you use an ObservableCollection, changes will be reflected
(also, remove the space in { Binding}

Xamarin Forms ListView cancel ItemSelected event

With the Xamarin Forms ListView, the ItemSelected event is fired each time an element is selected in the list.
Is there a way to cancel out of this event so that the new item isn't selected and the old item remains selected?
The use case is a master/detail type of view where selecting an item in the list changes the detail. But the detail view from the previous selection might have been altered and the user needs to decide to discard or save the previous changes before changing the List's SelectedItem.
#SushiHangover's suggestion to control the SelectionMode property and disable/enable the selection of the ListView is a good one. However, I have an alternate solution that will revert the ListView's selected item to the previous item for anyone who might have a similar need.
I will only post snippets of the solution, but they should be complete enough for someone else to learn and implement.
First, I am using FreshMVVM which provides (amongst many things), essentially, syntactic sugar over binding the View to the ViewModel. Also, the PropertyChanged nuget package creates the INotifyPropertyChanged boilerplate code at compile time. That is why you don't see the familiar XF patterns you normally see with that interface. AddINotifyPropertyChanged handles all that.
The solution to my problem is a dedicated, generic ListViewModel that can be bound to any ListView that needs the ability "roll back" a selection changed event. It binds to the Items collection. Additionally the SelectedItem property is bound to the control as well.
The constructor takes a Func which is called to determine if it's ok to move the selection or not.
[AddINotifyPropertyChangedInterface]
public class ListViewModel<T>
{
private Func<bool> _beforeChangeValidator;
private Action _afterChange;
public ListViewModel(Func<bool> beforeChangeValidator, Action afterChange)
{
_beforeChangeValidator = beforeChangeValidator;
_afterChange = afterChange;
_changing = false;
}
public int SelectedIndex { get; set; }
public T SelectedItem { get; set; }
public ObservableCollection<T> Items { get; set; }
private bool _changing;
public Command SelectedItemChanged
{
get
{
return new Command((args) =>
{
if (!_changing)
{
if (_beforeChangeValidator())
{
SelectedIndex = ((SelectedItemChangedEventArgs)args).SelectedItemIndex;
}
}
_changing = false;
});
}
}
public void RevertSelectedItemChanged()
{
_changing = true;
SelectedItem = Items[SelectedIndex];
}
}
And the code in the parent ViewModel has the Func (TagListBeforeChange) that determines if it's ok to move the selection or not. In this case I am checking if the last selected item has been changed, and if it has, prompt the user for what to do.
public override void Init()
{
TagListViewModel = new ListViewModel<Tag>(TagListBeforeChange, null);
}
private bool TagListBeforeChange()
{
if (ActiveTag.HasChanged)
{
var confirmConfig = new ConfirmConfig()
{
Message = "Current tag has changed. Discard changes and continue?",
OkText = "Discard Changes",
CancelText = "Cancel",
OnAction = (result) =>
{
if (result)
{
_mapper.Map(TagListViewModel.SelectedItem, ActiveTag);
}
else
{
TagListViewModel.RevertSelectedItemChanged();
}
}
};
_userDialogs.Confirm(confirmConfig);
return false;
}
_mapper.Map(TagListViewModel.SelectedItem, ActiveTag);
return true;
}
And finally, here is the ListView control declaration...
<ListView ItemsSource="{Binding TagListViewModel.Items}"
SelectedItem="{Binding TagListViewModel.SelectedItem, Mode=TwoWay}">
<ListView.Behaviors>
<behaviors:EventHandlerBehavior EventName="ItemSelected">
<behaviors:InvokeCommandAction Command="{Binding TagListViewModel.SelectedItemChanged}" />
</behaviors:EventHandlerBehavior>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="8">
<Label Text="{Binding DisplayValue}" />
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Telerik UWP DataForm changing bound data and Updating

I'm currently using telerik in UWP to create list of items, i want to be able to use a browse button and update a certain piece of data in the Telerik-RadDataForm. I have all the bindings setup using MVVM and it displays data fine if it isn't edited on the code side. My XAML is setup as so:
<Data:RadDataForm x:Name="dataform"
HorizontalAlignment="Left" Grid.Row="0"
Grid.RowSpan="2" Grid.Column="2"
VerticalAlignment="Center" Width="454"
Item="{Binding CurrentSceneViewModel, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" CommitMode="Immediate"
ValidationMode="Immediate" Height="664" Margin="0,28" />
The CurrentSceneViewModel is:
public SceneViewModel CurrentSceneViewModel
{
get => _currentSceneViewModel;
set=> _currentSceneViewModel= value;
}
And the data i wish to change is :
public string FileName
{
get => _fileName;
set
{
Scene.SceneFile = value;
_fileName = Path.GetFileName(value);
OnPropertyChanged(nameof(FileName));
}
}
The problem i have is pushing this information to the user interface the code-behind doesn't seem to update the UI, even using PropertyChanged. I'm not sure what else to try ? And if this is something the RadDataform simply doesn't support. It should be noted FileName is a property of CurrentScene ViewModel.
public abstract class BaseViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected IPageNavigationService navservice = new PageNavigationService();
}

Data binding property not working

first of all sorry for my english.
I am developing a WP7 app, and I still haven't completely understood the data binding structure. I have a page that has some data obtained through data binding. Data is generated within the .cs, and it works fine.
But on another page I have some data that is obtained from data binding too, but I want it to come from a UI input text instead. It's simple, just a textbox and a textblock, so the user writes something on the textbox and so it shows up on the textblock that's on the same page. But it's not working, the textblock remains empty.
It's something like this:
<TextBox Name="TestInput">
<TextBlock Text="{Binding TestText}">
Above is what's on the XAML.
public partial class NewItem : PhoneApplicationPage
{
public String TestText { get; set; }
public NewItem()
{
InitializeComponent();
TestText = "TestInput.Text";
}
}
And this above is what's on the C#.
BUT!! It doesn't end here. Since the textblock wasn't showing anything, I ended desperately trying to assign some plain String to the TestText property. Like this:
TestText = "HELLO WORLD";
But when the app starts and the page loads, the textblock shows nothing. I just don't understand what am I missing, or doing wrong.
I will appreciate if someone could clear me up the databinding structure, or at least explain me what did I do wrong so I can figure it out myself.
Thanks in advance guys!
You have to assign the DataContext before you want the binding effect.So whenever there is a change in the text box, you write the code in the textchanged event.
this.DataContext=TestText
And one more change that you need to perform is that you are not actually setting the property.It should be like
TestText=TestInput.Text
for your understanding of binding i am putting a simple working example..just follow this..
this is you page on which you bind the data of textbox to someproperty textboxText..when you finished writing in this textbox.then all the writtentext automatically come ino this property. and this property is also binded to textbloack so when your textbloack got focus it will got to the get of the property and automatically fill it.
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Orientation="Horizontal" >
<TextBox x:Name="testTextbox" Height="50" Width="200" Text="{Binding TextboxText,Mode=TwoWay}" />
<TextBlock x:Name="testTextblock" Height="50" Width="1000" Text="{Binding TextboxText,Mode=OneWay}" Foreground="White" />
</StackPanel>
</Grid>
this is your page.cs class in which i have also shown you how to implement inotifyproperty changed..it will help you in future..
public sealed partial class MainPage : Page,INotifyPropertyChanged
{
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
private string _TextboxText;
public string TextboxText
{
get
{
return _TextboxText;
}
set
{
_TextboxText = value;
FirePropertyChanged("TextboxText");
testTextblock.UpdateLayout();
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected void FirePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

ListPicker does not 'tick' selected items when ItemsSource is binded to data fetched from service

I followed instructions at http://mobileworld.appamundi.com/blogs/andywigley/archive/2012/02/02/how-to-databind-selecteditems-of-the-listpicker-and-recurringdayspicker.aspx to bind SelectedItems in multiselect ListPicker.
When I bind ItemsSource to the ObservableCollection of objects created in the ViewModel, ListPicker works without any problems. However, when I bind ItemsSource to ObservableCollection fetched from WCF service, problems start. ListPicker displays all items properly, they can also be selected and displayed as selected on main screen. But when I click the picker to select again, ListPicker is not able to 'tick' selected items on the template.
I could rewrite the list of all elements fetched from service but I'm wondering if there is a neat way of solving that problem?
Project info: WP 7.0, WCF services, EntityFramework, lots of coffee
Entity class:
DataContract(IsReference=true)]
public class TypeOfDish
{
[DataMember]
public int TypeOfDishID { get; set; }
[DataMember]
public string NameToDisplay { get; set; }
}
WCF Service:
[ServiceContract]
public interface IMyService
{
[OperationContract]
[ApplyDataContractResolver]
IEnumerable<TypeOfDish> GetDishTypes();
}
Xaml:
<StackPanel x:Name="DishTypeGroup" Orientation="Vertical" Width="400">
<helpers:ListPickerExtended x:Name="TypeOfDishPicker" >
ItemsSource="{Binding DishTypeList}"
SelectionMode="Multiple"
SelectedItems="{Binding SelectedDishTypes, Mode=TwoWay}"
Margin="8,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Center">
<toolkit:ListPicker.FullModeItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=NameToDisplay}" />
</DataTemplate>
</toolkit:ListPicker.FullModeItemTemplate>
</helpers:ListPickerExtended>
</StackPanel>
ListPickerExtended.cs
public class ListPickerExtended : ListPicker
{
public new IList SelectedItems
{
get
{
return (IList)GetValue(SelectedItemsProperty);
}
set
{
base.SetValue(SelectedItemsProperty, value);
}
}
}
ViewModel
public ObservableCollection<TypeOfDish> DishTypeList
{
get
{
//myModel.DichTypes is loaded with data from calling
//GetDishTypesAsync() on my service client
return myModel.DishTypes;
}
}
ObservableCollection<object> _selectedDishes = new ObservableCollection<object>();
public ObservableCollection<object> SelectedDishTypes
{
get { return _selectedDishes; }
set
{
if (_selectedDishes == value) { return; }
_selectedDishes = value;
RaisePropertyChanged("SelectedDishTypes");
}
}
I've just found solution.
I was fetching data from the service every time the page was loaded. When returning from full mode template in ListPicker, the main page was reloaded, resetting DishTypesList property using newly fetched data. Although ListPicker.SelectedItems were still set, the new object were not matching the objects in them.
I just needed to move loading data into View Model constructor and ListPicker started working correctly.

Resources