ReactiveUI: React to changes of a property - c#

I am making my first steps with ReactiveUI but I am not able to event get a very basic example to work. I want to execute an task as soon as the property "SearchTerm" changes. I followed the instructions on the github page of ReactiveUI ("a compelling example").
I have a ViewModel with the property SearchTerm which is bind to a TextBox in my view. If I update the content of the TextBox the property is updated as expected (I used UpdateSourceTrigger=PropertyChanged).
The code in my observables never fires:
public class MainWindowViewModel: ReactiveObject
{
public string SearchTerm
{
get { return m_SearchTerm; }
set { this.RaiseAndSetIfChanged(ref m_SearchTerm, value); }
}
private string m_SearchTerm;
public MainWindowViewModel()
{
SearchResults = new List<string>();
var canSearch = this.WhenAny(x => x.SearchTerm, x => !string.IsNullOrWhiteSpace(x.GetValue()));
var search = ReactiveCommand.CreateAsyncTask(canSearch,
async _ => {
// this is never called
return await dosearch(this.SearchTerm);
});
search.Subscribe(results => {
// this is never called too
SearchResults.Clear();
SearchResults.AddRange(results);
});
}
private async Task<List<string>> dosearch(string searchTerm)
{
await Task.Delay(1000);
return new List<string>() { "1", "2", "3" };
}
public List<string> SearchResults { get; private set; }
}

The code inside your command never fires, because you are not invoking the command anywhere. You have to bind your command to any event (like input text changing, button click, etc).
First, expose your command from ViewModel:
public ReactiveCommand<List<string>> Search { get; private set; }
next, assign it in constructor:
this.Search = ReactiveCommand.CreateAsyncTask(canSearch,
async _ => {
return await dosearch(this.SearchTerm);
});
and finally, invoke the command when the input changes (this is the crucial missing part of your code):
this.WhenAnyValue(x => x.SearchTerm)
.InvokeCommand(this, x => x.Search);
Put the abouve code in the constructor.
Note that this will fire searches constantly when the user types. To fix this, you can use an Rx operator called Throttle, as seen in the example you linked to.

Related

C#, Xamarin Forms - ObservableCollection of custom Object updates but does not fire property changed events

I have a collection that gets populated with produce. Once the collection is populated, a BindableLayout/DataTemplate bound to a StackLayout will display the items and the user will be prompted with the option to change the Price property of a stock item by typing into an Entry.
A user can type into the provided Entry box to change Price property of each StockInfo object in the collection, and the change WILL SUCCESSFULLY be applied to the Observable Collection, BUT it WILL NOT fire the setter/property changed event of the Observable Collection.
I need the property changed event to fire so that I can effectively execute other parts of my code, but since it won't the fire setter or property changed of the collection, it never gets the chance to tell other parts of my code to do things.
namespace Test
{
public class Testing : BaseContentPage, INotifyPropertyChanged
{
public class StockInfo : BaseContentPage, INotifyPropertyChanged
{
private string description;
public string Description
{
get => description;
set
{
description = value;
OnPropertyChanged();
}
}
private int price;
public int Price
{
get => price;
set
{
price = value;
OnPropertyChanged();
}
}
}
private ObservableCollection<StockInfo> stockItems = new ObservableCollection<StockInfo>();
public ObservableCollection<StockInfo> StockItems
{
get => stockItems;
set
{
stockItems = value;
OnPropertyChanged();
OnPropertyChanged("SumPrices");
}
}
public double SumPrices
{
get
{
return StockItems.Sum(p => p.Price);
}
}
DataTemplate StockTemplate = new DataTemplate(() =>
{
return new StackLayout
{
Orientation = StackOrientation.Horizontal,
Children =
{
new Entry
{
}.Bind(Entry.TextProperty, path: "Description")
,
new Entry
{
Keyboard = Keyboard.Numeric
}.Bind(Entry.TextProperty, path: "Price")
}
};
});
public Testing()
{
BindingContext = this;
StockItems.Add(new StockInfo { Description = "Milk", Price = 20 });
StockItems.Add(new StockInfo { Description = "Cheese", Price = 15 });
Content = new StackLayout
{
Children =
{
new StackLayout
{
}.Invoke(layout => BindableLayout.SetItemTemplate(layout, StockTemplate))
.Bind(BindableLayout.ItemsSourceProperty, path: "StockItems")
,
new Label
{
}.Bind(Label.TextProperty, path: "SumPrices")
}
};
}
}
}
If I put a debugger stop line inside the get/set of the "Description" property in the StockInfo class and then type in the Entry, the debugger will pick it up and stop the program for debugging.
But if I put a debugger stop on a line some where in the set/get of the Observable Collection, the program will not stop inside of it.
*** Edits Below ***
I modified the code so that StockInfo now has a property that includes the price of a product. I also added a variable called SumPrices which will return the Sum of Price within StockItems using LINQ. The first time the page loads, the sum is calculated and the result is correct, but if I change the Entry box that the property is bound to for each object, it has no effect and the SumPrices variable never changes.
Ideally, I'd simply like for the Observable Collection to fire its setter/property change events whenever an Object's property within the collection is changed.
New Update Here
You cannot fire the setter of ObservableCollection when a property of an item in this collection has changed. I've searched so many info from the Internet and found a question similar to yours: ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged). Bob Sammers abstract and define a new FullyObservableCollection class and put forward a pretty robust solution, including some of the techniques in other answers. This new class could get notified when a property of item has been changed. I have tested it and worked well.
Simply used it like the following code:
private FullyObservableCollection<StockInfo> stockItems = new FullyObservableCollection<StockInfo>();
public FullyObservableCollection<StockInfo> StockItems
{
get => stockItems;
set
{
stockItems = value;
OnPropertyChanged();
}
}
public Testing ()
{
...
StockItems.ItemPropertyChanged += StockItems_ItemPropertyChanged;
...
}
private void StockItems_ItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(SumPrices));
}
Another workaround is what i've suggested in my previous answer, which is using TextChanged event handler.
In Datatemplate, add an EventHandler for entry:
new Entry
{
}.Bind(Entry.TextProperty, path: "Description",BindingMode.TwoWay)
.Invoke(entry=>entry.TextChanged+=Entry_TextChanged)
private void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
OnPropertyChanged(nameof(SumPrices));
}
For more info, you could refer to Xamarin.Forms - CollectionView sample
Hope it works for you.

CollectionView ItemsSource binding visual update (Xamarin.Forms)

I tried to make some MVVM pattern into my app, and i ran into a problem with hte visual representation of data. The data if the binded observablecollecrion is updated, but the visual is not.
some code:
ViewModel:
public class HlavnaViewModel : BaseViewModel
{
public HlavnaViewModel()
{
}
private Doklady _selectedDok;
public Doklady vm_selectedDok
{
get => _selectedDok;
set
{
_selectedDok = value;
OnPropertyChanged(nameof(vm_selectedDok));
update_polozky();
}
}
public async void update_polozky()
{
Polozky dok = new Polozky() { id_doklad = _selectedDok.id };
ObservableCollection<Polozky> pol = new ObservableCollection<Polozky>(await App.Database.GetPolozkyAsync(dok));
vm_polozky = pol;
}
private ObservableCollection<Polozky> _polozky;
public ObservableCollection<Polozky> vm_polozky
{
get => _polozky;
set
{
_polozky =value;
OnPropertyChanged(nameof(vm_polozky));
}
}
}
in the XAML:
<CollectionView x:Name="polozky" SelectionMode="Single" ItemsSource="{Binding vm_polozky}">...
BaseViewModel:
public class BaseViewModel : INotifyPropertyChanged
{
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
finally in View:
public Hlavna()
{
InitializeComponent();
hvm = new HlavnaViewModel();
this.BindingContext = hvm;
}
if i select a row in CollectionView where the vm_selectedDok binding is set, it selects that item, fires the update_polozky(), the vm_polozky gets populated with the right data, but the visual just dont shows the items from vm_polozky.
Ive read couple of similar questions, but i cant figure out where i made a mistake.
EDIT:
so the problem was somewhere else, i had the grid.rowdefinitions set just wrong, therefore the grid was outside of the visible area.
#ToolmakerSteve made good suggestions on calling async/await, please read his answer.
There are two alternative ways to fix this.
One way is Gerald's answer. This is fine for small collections, but might be slower if there are many items being added.
The second way is to do what you've done - replace the collection. But there is a fix needed in your code.
The way you've called update_polozky won't work reliably. You don't start the async/await sequence inside an await.
Replace:
update_polozky();
With:
Device.BeginInvokeOnMainThread(async () => {
await update_polozky();
});
OPTIONAL: Might also make this change. (Though it shouldn't be necessary.) This gives a good place to put a breakpoint, to see whether "result" gets the expected contents.
Replace:
ObservableCollection<Polozky> pol = new ObservableCollection<Polozky>(await App.Database.GetPolozkyAsync(dok));
With:
var result = await App.Database.GetPolozkyAsync(dok);
ObservableCollection<Polozky> pol = new ObservableCollection<Polozky>(result);
IMPORTANT NOTES FOR OTHER CODERS:
The second approach ("replace the collection") relies on OnPropertyChanged(nameof(vm_polozky)); in the setter of the ObservableCollection.
You have that, so not an issue for you. I mention this for anyone else who might adapt this code.
For example, I've seen people attempt to set the private value directly, e.g.:
// Don't do this to replace the collection. XAML won't know you changed it!
_myPrivateField = new ObservableCollection<MyItem>(result);
I've also seen people try to have an ObservableCollection without a setter:
// This is okay UNLESS you replace the collection - in which case you need `OnPropertyChanged(nameof(MyCollection))` somewhere:
public ObservableCollection<MyItem> MyCollection { get; set; }
Basically it comes down to, don't do this: ObservableCollection<Polozky> pol = new ObservableCollection<Polozky>(await App.Database.GetPolozkyAsync(dok));
Whenever you create a new ObservableCollection it will lose the databinding to the UI. Clear your ObservableCollection with .Clear() and add new items to it with a for loop. For example:
public async void update_polozky()
{
Polozky dok = new Polozky() { id_doklad = _selectedDok.id };
var results = await App.Database.GetPolozkyAsync(dok);
vm_polozky.Clear();
foreach(var item in results)
vm_polozky.Add(item);
}

Refresh data in windows store app (MVVM pattern)

I am developing a windows 8 store app, using C#/xaml, with the MVVM pattern.
I want to refresh a page automatically every 30 seconds, after a search I came up with this, but how do I implement it into a MVVM page?
Edit: As of Charleh's answer I came up with this:
var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
var period = TimeSpan.FromSeconds(30);
var timer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
{
await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
RefreshOrdersList();
});
}, period);
But the VS compiler marks an 'Error' under the dispatcher.RunAsync() function: "The 'await' operator can only be used within an async lambda expression". When I remove the 'await' keyword the application runs with a warning saying "because this call is not awaited, execution of the current method continues before the call is completed, Consider apply the 'await' operator to the result of the call".
RefreshOrdersList function -gets all orders and details from WCF service:
private async void RefreshOrdersList()
{
var orders = await proxy.GetAllOrdersAsync();
IList<OrderCart> orderModel = new List<OrderCart>();
foreach (var order in orders)
{
OrderCart oc = new OrderCart { Id = order.Id, FullPrice = Convert.ToDouble(order.Total), TableId = order.TableId, IsDone = false, OrderTime = (DateTime)order.StartOrderTime };
order.OrderDetails = await proxy.GetOrderDetailsByOrderIdAsync(order.Id);
oc.OrderCartItems = new ObservableCollection<OrderCartItem>();
foreach (var orderDetail in order.OrderDetails)
{
var course = await proxy.GetCourseByIdAsync(orderDetail.CourseId);
OrderCartItem oci = new OrderCartItem { Quantity = orderDetail.Amount, Course = new Course(course) };
oc.OrderCartItems.Add(oci);
}
orderModel.Add(oc);
}
var sortOrder = orderModel.OrderByDescending(x => x.Id);
Items = new ObservableCollection<OrderCartViewModel>(sortOrder.
Select(o => new OrderCartViewModel(o))
.ToList());
Items property:
public ObservableCollection<OrderCartViewModel> Items
{
get { return _items; }
private set { SetProperty(ref _items, value); }
}
Anyone??
The dispatcher is found on UI types (controls etc) and it's job is to sync to the UI thread so you will only find this in your views.
Is there any reason the whole view needs to reload?
If you are using the MVVM pattern, you can just do your work asyncronously in the viewmodel, and then update your properties as per usual.
e.g.
class SomeViewModel
{
IEnumerable<Results> Results { get; set; } // Obviously this would actually need to raise PropertyChanged from INotifyPropertyChanged in order to refresh any bindings
public SomeViewModel()
{
var period = TimeSpan.FromMinutes(1);
var timer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
{
// do your query/work here
DoSomeWorkAsync();
},
period);
}
void DoSomeWorkAsync()
{
// Get the data
someService.GetResults((result) => { Results = result.Data; });
}
}
The binding system will take care of the UI update. I assume you just want to go off to the datasource (a web service maybe?) and get new results every 30 seconds, do you need more than just a data/binding update?
Disclaimer: Didn't test this, and you'd also need to be aware of exceptions thrown within the thread pool work item and handle appropriately
Edit: In response to your question about the binding system
The WPF/RT/SL binding system looks for data sources that implement certain interfaces - one of these is INotifyPropertyChanged. As long as the datasource you are binding to implements this (and in this case your datasource is the viewmodel), and you raise a PropertyChanged event for the property when it changes, the binding system will know to refresh
I have a code snippet in Visual Studio (well actually Resharper as it works a bit better) that writes the property for me - I think there is one included in VS but it should look something like this:
private int _someInt;
public int SomeInt
{
get { return _someInt; }
set
{
if(_someInt != value)
{
_someInt = value;
// Helper function in the class which checks to see if propertychanged has any subscribers
OnPropertyChanged("_someInt");
}
}
}
Note: There are better implementations of this using Expressions instead of 'magic strings' or using new language features. If you are using the latest c# version the addition of CallerMemberName attribute means you don't need to specify the property name
Edit 2: Ok a complete example may look like this
public class SomeViewModel : INotifyPropertyChanged
{
#region Properties (that support INotifyPropertyChanged)
private IEnumerable<Result> _results;
public IEnumerable<Result> Results
{
get
{
return _results;
}
set
{
if (value != _results)
{
_results = value;
OnPropertyChanged("Results");
}
}
}
#endregion
// A reference to the service that will get some data
private IDataService _dataService;
public SomeViewModel(IDataService dataService)
{
_dataService = dataService;
var period = TimeSpan.FromMinutes(1);
var timer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
{
// do your query/work here
GetData();
},
period);
}
#region Data fetch method
/// <summary>
/// Method to get some data - waits on a service to return some results
/// </summary>
void GetData()
{
// Get the data
_dataService.GetResults((result) =>
{
// Try this instead
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.Invoke(() => {
Results = result.Data; }
});
}
#endregion
#region INotifyPropertyChanged
// Note: usually most MVVM frameworks have a base class which implements the INPC interface for you (such as PropertyChangedBase in Caliburn Micro)
// so it might be worth fishing around in your framework for it if you are using one.. If you aren't using a framework then you are a braver man than me
// start using one now!
/// <summary>
/// The property changed event
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// The helper function to raise the event
/// </summary>
/// <param name="propertyName">Name of the property that changed (to tell the binding system which control to update on screen based on bindings)</param>
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Edit: update code above. Let me know if you can't get hold of the dispatcher still
Just need to add the 'async' keyword var timer = ThreadPoolTimer.CreatePeriodicTimer( async (source)=>, and no errors or warning were detected:
var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
var period = TimeSpan.FromSeconds(30);
var timer = ThreadPoolTimer.CreatePeriodicTimer( async (source) =>
{
await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
RefreshOrdersList();
});
}, period);

Observing changes only within a window that commits successfully

Suppose I have the following class:
public class Person : ReactiveObject, IEditableObject
{
private string name;
private string nameCopy;
public string Name
{
get { return this.name; }
set { this.RaiseAndSetIfChanged(ref this.name, value); }
}
public void BeginEdit()
{
this.nameCopy = this.name;
}
public void CancelEdit()
{
this.name = this.nameCopy;
}
public void EndEdit()
{
}
}
Now suppose I want to create an observable sequence (of Unit) that "ticks" whenever a change is committed to Name. That is, I only care about changes to Name that occur between a call to BeginEdit and and a subsequent call to EndEdit. Any changes prior to a call to CancelEdit should be ignored and the sequence should not tick.
I'm struggling to get my head around how I would do this with Rx. It seems I would need state in the pipeline somewhere in order to know whether the change occurred during the window of BeginEdit/EndEdit calls. I suppose I could timestamp everything and compare timestamps, but that seems a nasty hack.
I came pretty close using a dedicated Subject for edit actions along with Observable.Merge:
public class Person : ReactiveObject, IEditableObject
{
private readonly Subject<EditAction> editActions;
private readonly IObservable<Unit> changedDuringEdit;
private string name;
private string nameCopy;
public Person()
{
this.editActions = new Subject<EditAction>();
var nameChanged = this.ObservableForProperty(x => x.Name).Select(x => x.Value);
var editBeginning = this.editActions.Where(x => x == EditAction.Begin);
var editCommitted = this.editActions.Where(x => x == EditAction.End);
this.changedDuringEdit = nameChanged
.Buffer(editBeginning, _ => editCommitted)
.Where(x => x.Count > 0)
.Select(_ => Unit.Default);
}
public IObservable<Unit> ChangedDuringEdit
{
get { return this.changedDuringEdit; }
}
public string Name
{
get { return this.name; }
set { this.RaiseAndSetIfChanged(ref this.name, value); }
}
public void BeginEdit()
{
this.editActions.OnNext(EditAction.Begin);
this.nameCopy = this.name;
}
public void CancelEdit()
{
this.editActions.OnNext(EditAction.Cancel);
this.Name = this.nameCopy;
}
public void EndEdit()
{
this.editActions.OnNext(EditAction.End);
}
private enum EditAction
{
Begin,
Cancel,
End
}
}
However, if several changes are cancelled, and then one is committed, the observable ticks several times on commit (once for each prior cancellation, and once again for the commit). Not to mention the fact that I get a List<Unit> which I don't actually need. In a way, this would still satisfy my use case, but not my curiosity or sense of code aesthetic.
I feel like Join should solve this fairly elegantly:
var nameChanged = this.ObservableForProperty(x => x.Name).Select(_ => Unit.Default);
var editBeginning = this.editActions.Where(x => x == EditAction.Begin);
var editCommitted = this.editActions.Where(x => x == EditAction.End);
var editCancelled = this.editActions.Where(x => x == EditAction.Cancel);
var editCancelledOrCommitted = editCancelled.Merge(editCommitted);
this.changedDuringEdit = editBeginning
.Join(nameChanged, _ => editCancelledOrCommitted, _ => editCancelledOrCommitted, (editAction, _) => editAction == EditAction.End)
.Where(x => x)
.Select(_ => Unit.Default);
But this doesn't work either. It seems Join is not subscribing to editCancelledOrCommitted, for reasons I don't understand.
Anyone have any ideas how to go about this cleanly?
Here's how I'd do it:
IObservable<Unit> beginEditSignal = ...;
IObservable<Unit> commitSignal = ...;
IObservable<Unit> cancelEditSignal = ...;
IObservable<T> propertyChanges = ...;
// this will yield an array after each commit
// that has all of the changes for that commit.
// nothing will be yielded if the commit is canceled
// or if the changes occur before BeginEdit.
IObservable<T[]> commitedChanges = beginEditSignal
.Take(1)
.SelectMany(_ => propertyChanges
.TakeUntil(commitSignal)
.ToArray()
.Where(changeList => changeList.Length > 0)
.TakeUntil(cancelEditSignal))
.Repeat();
// if you really only want a `Unit` when something happens
IObservable<Unit> changeCommittedSignal = beginEditSignal
.Take(1)
.SelectMany(_ => propertyChanges
.TakeUntil(commitSignal)
.Count()
.Where(c => c > 0)
.Select(c => Unit.Default)
.TakeUntil(cancelEditSignal))
.Repeat();
You have a timing problem that I don't think you have articulated yet; when are you hoping for the changes to tick?
either as they occur
once the commit happens
The clear and obvious problem with 1) is that you don't know if the changes will be committed, so why would you raise them. IMO, this only leaves option 2). If the change is cancelled, then no event is raised.
Next question I have is, do you want each change raised? ie. for the process
[Begin]-->[Name="fred"]-->[Name="bob"]-->[Commit]
Should this raise 1 or 2 events when the Commit is made? As you are only pushing the token type Unit, it seems redundant to push two values. This now leads me to think that you just want to push a Unit value when EndEdit() is executed and the values have changed.
This leaves us with a painfully simple implementation:
public class Person : ReactiveObject, IEditableObject
{
private readonly ISubject<Unit> changedDuringEdit = new Subject<Unit>();
private string name;
private string nameCopy;
public string Name
{
get { return this.name; }
set { this.RaiseAndSetIfChanged(ref this.name, value); }
}
public void BeginEdit()
{
this.nameCopy = this.name;
}
public void CancelEdit()
{
this.name = this.nameCopy;
}
public void EndEdit()
{
if(!string.Equals(this.nameCopy, this.name))
{
changedDuringEdit.OnNext(Unit.Default);
}
}
public IObservable<Unit> ChangedDuringEdit
{
get { return this.changedDuringEdit.AsObservable(); }
}
}
Is this what you are looking for? If not can you help me understand the complexities I am missing? If it is then I would be keen to flesh this out so that I wasn't recommending using Subjects :-)

How do I update the parent viewmodel when child viewmodel is updated

In my first view model (renamed to MainViewModel) I have a list of ActionViewModels.
In my xaml i have a listbox which is bound to the list, in the listbox i have a template which binds to properties from the ActionViewModel.
So far so good and everything works.
When selecting one of the listitems i navigate to an ActionViewModel and pass the id with it.
The ActionViewModel retrieves information from a static list in memory from which the MainViewModel also retrieved the information to create the list of actionviewmodels.
So far still so good, i can edit the properties, all the bindings do work fine and i'm all happy.
By clicking the save button the information is gathered and stored in the static list.
When i hit the back button i go back to the list, but unfortunately the values showing there are still the same, is there some way to send a command to reload the items in the list? To pass a complete viewmodel as reference to a new ActionViewModel? Or some property which tells the parent 'this viewmodel in your list has been updated'?
I am sure the above text is a bit confusing, so here is some code to clarify it a bit (hopefully)
MainViewModel.cs
private List<ActionViewModel> _actionViewModels;
public List<ActionViewModel> ActionViewModels
{
get { return _actionViewModels; }
set { _actionViewModels = value; RaisePropertyChanged(() => ActionViewModels); }
}
private Cirrious.MvvmCross.ViewModels.MvxCommand<int> _navigateToAction;
public System.Windows.Input.ICommand NavigateToAction
{
get
{
_navigateToAction = _navigateToAction ?? new Cirrious.MvvmCross.ViewModels.MvxCommand<int>((action) => NavigateToTheDesiredAction(action));
return _navigateToAction;
}
}
private void NavigateToTheDesiredAction(int action)
{
ShowViewModel<ActionViewModel>(new { id = action });
}
// Get DTOs from server or from cache and fill the list of ActionViewModels
public async Task Load()
{
ActionService actionService = new ActionService();
List<ActionViewModel> actionViewModels = new List<ActionViewModel>();
MyActions = await actionService.GetMyActions();
foreach (ActionDTO action in MyActions)
{
ActionViewModel actionViewModel = new ActionViewModel();
await actionViewModel.Load(action.id);
actionViewModels.Add(actionViewModel);
}
ActionViewModels = actionViewModels;
}
ActionViewModel.cs
public int ID
{
get { return TheAction.id; }
set { TheAction.id = value; RaisePropertyChanged(() => ID); }
}
public string Title
{
get { return TheAction.Title; }
set { TheAction.Title = value; RaisePropertyChanged(() => Title); }
}
public async Task Load(int actionId)
{
ActionDTO TheAction = await actionService.GetAction(actionId);
this.ID = TheAction.id;
this.Title = TheAction.Title;
}
private Cirrious.MvvmCross.ViewModels.MvxCommand _save;
public System.Windows.Input.ICommand Save
{
get
{
_save = _save ?? new Cirrious.MvvmCross.ViewModels.MvxCommand(PreSaveModel);
return _save;
}
}
private void PreSaveModel()
{
SaveModel();
}
private async Task SaveModel()
{
ValidationDTO result = await actionService.SaveAction(TheAction);
}
ActionService.cs
public static List<ActionDTO> AllActions = new List<ActionDTO>();
public async Task<ActionDTO> GetAction(int actionId)
{
ActionDTO action = AllActions.FirstOrDefault(a => a.id == actionId);
if (action == null)
{
int tempActionId = await LoadAction(actionId);
if (tempActionId > 0)
return await GetAction(actionId);
else
return new ActionDTO() { Error = new ValidationDTO(false, "Failed to load the action with id " + actionId, ErrorCode.InvalidActionId) };
}
return action;
}
private async Task<int> LoadAction(int actionId)
{
ActionDTO action = await webservice.GetAction(actionId);
AllActions.Add(action);
return action.id;
}
public async Task<ValidationDTO> SaveAction(ActionDTO action)
{
List<ActionDTO> currentList = AllActions;
ActionDTO removeActionFromList = currentList.FirstOrDefault(a => a.id == action.id);
if (removeActionFromList != null)
currentList.Remove(removeActionFromList);
currentList.Add(action);
AllActions = currentList;
return await webservice.SaveAction(action);
}
There are 3 ways I can think of that would allow you to do this.
The ActionService could send out some sort of notification when data changes. One easy way to do this is to use the MvvmCross Messenger plugin. This is the way the CollectABull service works in CollectionService.cs in the N+1 days of mvvmcross videos (for more info watch N=13 in http://mvvmcross.wordpress.com)
This is the approach I generally use. It has low overhead, uses WeakReferences (so doesn't leak memory), it is easily extensible (any object can listen for changes), and it encourages loose coupling of the ViewModel and Model objects
You could implement some kind of Refresh API on the list ViewModel and could call this from appropriate View events (e.g. ViewDidAppear, OnNavigatedTo and OnResume).
I don't generally use this approach for Refreshing known data, but I have used it for enabling/disabling resource intensive objects - e.g. timers
For certain shape of model data (and especially how often it changes), then I can imagine scenarios where this approach might be more efficient than the messenger approach.
You could extend the use of INotifyPropertyChanged and INotifyCollectionChanged back into your model layer.
I've done this a few times and it's worked well for me.
If you do choose this approach, be careful to ensure that all Views do subscribe to change events using WeakReference subscriptions such as those used in MvvmCross binding - see WeakSubscription. If you didn't do this, then it could be possible for the Model to cause Views to persist in memory even after the UI itself has removed them.

Categories