ViewModel Initialize method for different Views - c#

I have a TabbedPage which shows deliveries in progress and finished deliveries. The model for both views is the same, only the service method from where we get the data is different, so I would like to reuse the ViewModel.
Would it be a good solution to reuse the ViewModel by passing some navigation data into my InitializeAsync method that would allow me to decide which service method to use to get the data for the view?
I would override OnCurrentPageChanged in TabbedPage View's code-behind and Initialize the ViewModel from there
TabbedPageView.xaml.cs
protected override async void OnCurrentPageChanged()
{
base.OnCurrentPageChanged();
if (!(CurrentPage.BindingContext is TabbedPageViewModel tabbedPageViewModel)) return;
if (CurrentPage == DeliveriesInProgress)
{
await tabbedPageViewModel.InitializeAsync("DeliveriesInProgress");
}
else if (CurrentPage == FinishedDeliveries)
{
await tabbedPageViewModel.InitializeAsync("FinishedDeliveries");
}
}
TabbedPageViewModel.cs
public async Task InitializeAsync(object navigationData)
{
if (navigationData is string deliveryType)
{
if (deliveryType == "InProgress")
{
Deliveries = await _deliveryService.GetDeliveriesInProgress();
}
else if (deliveryType == "Finished")
{
Deliveries = await _deliveryService.GetFinishedDeliveries();
}
}
}
What could be alternative solutions?

The best way is to use two different properties in your viewmodel. Then you can bind the two different views in the tabs to the associated property.
In your viewmodel:
public ObservableCollection<MyDeliveryModel> FinishedDeliveries;
public ObservableCollection<MyDeliveryModel> DeliveriesInProgress;
Know you can add two methods to load the data for those properties:
public async Task RefreshFinishedAsync()
{
// Your logic to load the data from the service
}
public async Task RefreshInProgressAsync()
{
// Your logic to load the data from the service
}
And then in your TabbedPage-Event:
if (CurrentPage == DeliveriesInProgress)
{
await tabbedPageViewModel.RefreshInProgressAsync();
}
else if (CurrentPage == FinishedDeliveries)
{
await tabbedPageViewModel.RefreshFinishedAsync();
}
With this solution you can separate the data and you don't need to reload the whole data everytime you change the tabs. You can check if there is already some data in the collection, and if so... just don't reload the data. Just do it if the user wants it.
This improves the performance and the "wait-time" for the user.
Or as an alternative:
Load all data at once and just filter the data for the two collection-properties. This reduces the service-calls.

You can accomplish this by using a base viewmodel and a view model for each tab that uses the base. The base then holds your commands and deliveries. you bind each tabbed page to the viewmodel for that page so you won't need to check on tab changed. When you construct each viewmodel, pass in the information needed to base to know how to query the data. For each tabbed view, if the views are the same for in progress and finished, use a partial view and put it in both tabbed pages. This gives flexibility in the long run.
public class InProgressDeliveriesViewModel: BaseDeliveryViewModel{
public InProgressDeliveriesViewModel():base(filterParams){}
}
public class FinishedDeliveriesViewModel: BaseDeliveryViewModel{
public FinishedDeliveriesViewModel():base(filterParams){}
}
public class BaseDeliveryViewModel{
private FilterObjectOfSomeSort _filterParams;
public BaseDeliveryViewModel(filterParams whatever){
//use these params to filter for api calls, data. If you are calling the same
//endpoint pass up the filter
_filterParams = whatever;
}
public ObservableCollection<MyDeliveryModel> Deliveries {get;set;}
public async Task LoadDeliveries(){
//use the filter params to load correct data
var deliveries = await apiClient.GetDeliveries(filterParams); //however you
//are gathering data
}
.... All of your other commands
}

Related

Xamarin.Forms variable navigation

I'm currently making a stock control app for Android with Xamarin.Forms.
I have an extra page just for entering the Pruduct ID. I want to use that page in two scenarions:
You pick an product to add/remove them from stock
You pick an product to tell the office that new ones have to be ordered
After picking the product you will be redirectet to:
The "DetailStockmovementPage" in case 1
The "DetailOrderPage" in case 2
The Problem is, that the next Page depends on what button was clicked before.
In my ViewModel I have the following Code to navigate (for scenario 1):
public async Task GoToNextPage(Product product)
{
await Navigation.PushAsync(new DetailStockmovementPage(product), true);
}
How can I make the next Page variable? Can I define a variable "NextPage" in my ViewModel, set it from the View like "vm.NextPage = DetailStockmovementPage" and Navigate like
public async Task GoToNextPage(Product product)
{
await Navigation.PushAsync(new NextPage(product), true);
}
I know that does not work, but I think that poor try made clear what I want to achieve. I mean I could push a string for each page and make an if-query before navigating, but I don't think thats an good way to do it. How can I vary the page to be navigated to?
You could pass the user's choice from the first page into the page where they select the product, and then use that information to decide which page to navigate to. For example:
In your App.cs file add an enum:
public enum NavTarget
{
Order,
Stockmovement
}
Define a property in your VM for the target selected in your menu page:
public NavTarget Target { get; set; }
...and use it in your navigation method:
public async Task GoToNextPage(Product product)
{
if (Target == NavTarget.Stockmovement)
await Navigation.PushAsync(new DetailStockmovementPage(product), true);
else
await Navigation.PushAsync(new WhateverItsCalledPage(product), true);
}
Then set that property in the constructor for your ProductSelectionPage:
public ProductSelectionPage(NavTarget target)
{
InitializeComponent();
// some code that sets the VM for this page
// ...
vm.Target = target;
}

WPF: Execute some code asynchronously in .NET 3.5

I have a MVVM WPF app. I have a window, let's say "LvWindow", with a listview that is loaded from data comming from a database. From main window "MainWindow", I have a menu, which has some options. When I select the option to access "LvWindow", it is open. Then from ViewModel, in the constructor I have a call to a database from which I request some data that then I load into the listview.
My goal is to make the process to request data from database and then load it in the listview asynchronous. I want this in order to not block the whole app, I mean, during this window is loaded, user can go to the main window menu and select to open another type of window. Windows are open in tabs.
While the process of requesting data from database and being loaded into listview in window "LvWindow", I show a splash saying "Loading" on it(in fact this is a rectangle with zindex set to a higher number to avoid user can interact with listview until it is completely loaded). This splash will be closed when the listview is loaded with the data comming from database.
So to make the process asynchronous, I know in winforms it can be done with delegates by using beginInvoke, endInvoke and callbacks methods, see here.
Also, another possibility is to use a background worker, like posted here.
So in WPF which is the best way to do it? using delegates as winforms or background workers?
ATTEMPT #1:
I have tried XANIMAX solution as this:
public class TestViewModel : BaseViewModel
{
private static Dispatcher _dispatcher;
public ObservableCollection<UserData> lstUsers
public ObservableCollection<UserData> LstUsers
{
get
{
return this.lstUsers;
}
private set
{
this.lstUsers= value;
OnPropertyChanged("LstUsers");
}
}
public TestViewModel()
{
ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
{
var result = getDataFromDatabase();
UIThread((p) => LstUsers = result);
}));
}
ObservableCollection<UserData> getDataFromDatabase()
{
return this.RequestDataToDatabase();
}
static void UIThread(Action<object> a)
{
if(_dispatcher == null) _dispatcher = Dispatcher.CurrentDispatcher;
//this is to make sure that the event is raised on the correct Thread
_dispatcher.Invoke(a); <---- HERE EXCEPTION IS THROWN
}
}
but in line _dispatcher.Invoke(a) an exception is thrown:
TargetParameterCountException: the parameter count mismatch
UserData is my data model, it is a class with some public properties. Something like:
public class UserData
{
public string ID{ get; set; }
public string Name { get; set; }
public string Surname { get; set; }
// Other properties
}
so the problem is that the call to database is returning "RequestDataToDatabase" is returning a collection of UserData (ObservableCollection) so the exception is thrown.
I do not know how to solve it. Could you help me, please?
Final solution:
As XAMIMAX said in the comments:
Change the signature from static void UIThread(Action a) to static void UIThread(Action a)
modify UIThread((p) => LstUsers = result); by UIThread(() => LstUsers
= result);
As you can't await asynchronous methods in a constructor in C# 7.0 (but async Main is coming in 7.1) you can extract your async function calls to a separate function in your ViewModel and synchronously call this within your View's code-behind constructor, after you have created your ViewModel and assigned it to the View's DataContext:
public MainWindow()
{
this.vm = new MyViewModel();
this.DataContext = this.vm;
this.InitializeComponent();
this.vm.AsychronousFunctionToCallDatabase();
}
As XAMIMAX says, you want to implement a ViewModel to handle business logic between your View and your models. Then if your ViewModel implements INotifyPropertyChanged and and you set up Binding in your XAML to your properties in the ViewModel - then the display will refresh after the database call without blocking the UI thread. Note, if you have any collections populated from the database call then they should be of type ObservableCollection.
But as Kundan says, in your AsychronousFunctionToCallDatabase() function you should include an await statement or a create a Task on the line that calls the database - this will return control to the calling function (in this case, to the MainWindow constructor).
Here is one of the possible solutions for you.
In your View Model you would have something like this:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace VM
{
public class TestViewModel : BaseViewModel
{
private static Dispatcher _dispatcher;
List<object> ListToDisplay { get; set; }//INPC omitted for brevity
public TestViewModel()
{
ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
{
var result = getDataFromDatabase();
UIThread(() => ListToDisplay = result);
}));
}
List<object> getDataFromDatabase()
{
//your logic here
return new List<object>();
}
static void UIThread(Action a)
{
if(_dispatcher == null) _dispatcher = Dispatcher.CurrentDispatcher;
//this is to make sure that the event is raised on the correct Thread
_dispatcher.Invoke(a);
}
}
}
There are couple of options to run the method asynchronously.
async await - https://msdn.microsoft.com/library/hh191443(vs.110).aspx
Task Parallel Library :https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming

Loading async data from property setter

I'm not sure what is the best method to load async data when a bound property changes.
I have a ListView and when an item is selected I display new content with a detailed data. The detail item is obtained from an async method.
Data method:
private async void ShowDetailAsync()
{
if (SelectedItem?.Id != null)
{
detailViewModel.Item = await storage.GetDetailItemAsync(SelectedItem.Id);
}
else
{
detailViewModel.Item = null;
}
}
ViewModel property:
public GearItemListViewModel SelectedItem
{
get => selectedItem;
set { this.SetValue(ref selectedItem, value); ShowDetailAsync(); }
}
Now it works as fire-and-forget async method, but how would it be the best approach to load it without risk of desynchronized view and data when user rapidly browses the records in the ListView (first click takes longer to load the record than the other)?
Or is there another method how to switch and load detail items, without async call in the property setter?
I like to use the "asynchronous property" approach described in my MSDN article on async data binding.
In this case, your detailViewModel.Item property would change type from TItem to NotifyTask<TItem>, and your ShowDetailAsync becomes:
private void ShowDetail()
{
if (SelectedItem?.Id != null)
{
detailViewModel.Item = NotifyTask.Create(storage.GetDetailItemAsync(SelectedItem.Id);
}
else
{
detailViewModel.Item = null;
}
}
Note that the method is synchronous. It is synchronously starting an asynchronous operation.
With this change, your data binding would need to update to reference Item.Result instead of Item. You can then data bind to other properties such as Item.IsNotCompleted if you want to show a spinner/loading indicator, and Item.IsFaulted to notify the user of an error (with the previous async void approach, any errors would be raised directly on the UI thread).

UITableView to ObservableCollection binding breaks when the containing UIViewController is initialised for the second time

I'm using mvvmcross and xamarin to bind an ObservableCollection to a UITableView. The collection is updated in place using the Add, Remove and Move methods. These calls correctly trigger INotifyCollectionChanged events and the TableView is updated as expected the first time the view containing the table is shown. If the user navigates away from the original view as part of the normal application flow but later returns the correct data is loaded into the table but calls to add, move and remove no longer update the table.
The INotifyCollectionChanged events are still being fired when the collection is updated
If I manually subscribe to these events in my subclass of MvxStandardTableViewSource and try and call ReloadData on the UITableView still does not update
My presenter is creating a new instance of the viewmodel and view each time the page is visited.
I'm also using Xamarin-Sidebar (https://components.xamarin.com/view/sidebarnavigation) for navigation in my application with a custom presenter to load the views but as far as I can tell the view is initialised via exactly the same code path whether it's the first or subsequent visit.
My presenters Show() method looks like this:
public override void Show(MvxViewModelRequest request)
{
if (request.PresentationValues != null)
{
if(NavigationFactory.CheckNavigationMode(request.PresentationValues, NavigationFactory.ClearStack))
{
MasterNavigationController.ViewControllers = new UIViewController[0];
base.Show(request);
}
else if(NavigationFactory.CheckNavigationMode(request.PresentationValues, NavigationFactory.LoadView))
{
var root = MasterNavigationController.TopViewController as RootViewController;
var view = this.CreateViewControllerFor(request) as UIViewController;
root.SidebarController.ChangeContentView(view);
}
}
else
{
base.Show(request);
}
}
The binding in my ViewController looks like this:
public override void ViewDidLoad()
{
base.ViewDidLoad();
View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
var source = new TracksTableSource(TableView, "TitleText Title; ImageUrl ImageUrl", ViewModel);
TableView.Source = source;
var set = this.CreateBindingSet<TracksViewController, TracksViewModel>();
set.Bind(source).To(vm => vm.PlaylistTable);
set.Apply();
}
And my viewmodel is as below where PlaylistTable is a subclass of ObservableCollection with the Update method using add, move and remove to keep the collection up to date.
public class TracksViewModel : MvxViewModel
{
private readonly IPlaylistService _playlistService;
private readonly IMessengerService _messengerService;
private readonly MvxSubscriptionToken _playlistToken;
public PlaylistTable PlaylistTable { get; set; }
public TracksViewModel(IPlaylistService playlistService, IMessengerService messengerService)
{
_playlistService = playlistService;
_messengerService = messengerService;
if (!messengerService.IsSubscribed<PlaylistUpdateMessage>(GetType().Name))
_playlistToken = _messengerService.Subscribe<PlaylistUpdateMessage>(OnDirtyPlaylist, GetType().Name);
}
public void Init(NavigationParameters parameters)
{
PlaylistTable = new PlaylistTable(parameters.PlaylistId);
UpdatePlaylist(parameters.PlaylistId);
}
public async void UpdatePlaylist(Guid playlistId)
{
var response = await _playlistService.Get(playlistId);
PlaylistTable.Update(new Playlist(response));
}
private void OnDirtyPlaylist(PlaylistUpdateMessage message)
{
UpdatePlaylist(message.PlaylistId);
}
}
This setup works perfectly the first time the view is initialised and updates the table correctly, it's only the second and subsequent times the view is initialised that the table fails to update. Can anyone explain why the binding fails when it appears the view is created using the same techniques in both instances?
I can post additional code if required but I believe the issue will be how I'm using the presenter since the code I've not posted from PlaylistTable functions correctly in unit tests and on first viewing.

How to display complex data in MVP Passive View

I've been looking in the MVP pattern for a while, and managed to create some simple MVP-compliant applications.
I am now trying to apply the pattern to a more complex application, and I have some doubts on the best way of doing that.
My application has a single WinForm, with two buttons for loading two different kinds of data. My view interface looks like the following:
interface IView_MainForm
{
// Load input
//
event EventHandler<InputLoadEventArgs> LoadInput_01;
event EventHandler<InputLoadEventArgs> LoadInput_02;
bool Input01_Loaded { get; set; }
bool Input02_Loaded { get; set; }
}
The IView is referenced in my presenter via constructor injection:
public Presenter_MainForm(IView_MainForm view)
{
this.View = view;
this.View.LoadInput_01 += new EventHandler<InputLoadEventArgs>(OnLoadInput_01);
this.View.LoadInput_02 += new EventHandler<InputLoadEventArgs>(OnLoadInput_02);
}
So far, so good. When the user clicks any of the two buttons for loading data, a LoadInput_## event is raised, the Presenter is handling it, checks the input for errors and structures it according to my data model.
My next step would be displaying the processed data back in the View.
I'm striving to keep my View as passive and "dumb" as possible, assuming it knows nothing of the Presenter (it doesn't subscribe to its events, the Presenter sends data to the View by calling IView methods instead), let alone of the Model.
How am I supposed to populate a control like a TreeView, if the View has no idea of what the data model looks like?
Also, am I getting the whole MVP thing right, or is there anything I have missed?
There is nothing wrong with having complex type properties in your View. Let's say you have some ComplexType.
class ComplexType
{
public string ParentNode {get;set;}
public List<string> ChildNodes {get;set;}
// some other properties
}
Let's also assume ComplexType is data model for your TreeView. It is perfectly fine with MVP pattern to have properties on your View that will have ComplexType. So having something like this is perfectly fine
interface IView_MainForm
{
// Load input
//
event EventHandler<InputLoadEventArgs> LoadInput_01;
event EventHandler<InputLoadEventArgs> LoadInput_02;
bool Input01_Loaded { get; set; }
bool Input02_Loaded { get; set; }
ComplexType Input01Data {get;set;} // you might actually have some code in get/set setters
ComplexType Input02Data {get;set;} // you might actually have some code in get/set setters
public void SetInput01Data(ComplexType input01Data)
{
Input01Data = input01Data;
// some other stuff
}
}
And since your Model is for View that has 2 inputs, your Model could look something like this
public interface IModel
{
public ComplexType Input01Data {get;set;}
public ComplexType Input02Data {get;set;}
}
Now in your Presenter you would just handle event fired from View, populate Model and set properties on View
class Presenter
{
private IModel _myModel...
private IRepository _repository;
public Presenter(IView_MainForm view, IRepository repository)
{
_repository = repository;
this.View = view;
this.View.LoadInput_01 += new EventHandler<InputLoadEventArgs>(OnLoadInput_01);
this.View.LoadInput_02 += new EventHandler<InputLoadEventArgs>(OnLoadInput_02);
}
public void OnLoadInput_01(object sender, InputLoadEventArgs e)
{
// get data based on passed arguments (e.SomeProperty)
// construct IModel
myModel = _repository.GetData(e.SomeProperty);
// pass data to IView_MainForm
View.SetInput01Data(myModel.Input01Data);
}
}
And regarding your concern
I'm striving to keep my View as passive and "dumb" as possible,
assuming it knows nothing of the Presenter (it doesn't subscribe to
its events, the Presenter sends data to the View by calling IView
methods instead), let alone of the Model.
Your View still doesn't know anything about Presenter nor Model. It just fires events, get data from Presenter and binds its controls. And you have testability in place (please note this Unit Test is pseudo code, since I don't know how you retrieve data, what input you required in button click event etc...) .
[Test]
public void ShouldLoadInput01DataOnButtonClick()
{
// Arrange
IModel data = // create dummy data
Mock<IView_MainForm> clientsViewMock = new Mock<IView_MainForm>();
Mock<IRepository> clientsRepositoryMock = new Mock<IRepository>();
clientsRepositoryMock.Setup(repository => repository.GetData(something)).Returns(data.Input01Data);
var presenter = new Presenter(clientsViewMock.Object, clientsRepositoryMock .Object);
// Act
clientsViewMock.Raise(view => view.LoadInput01 += null, new InputLoadEventArgs());
// Assert
clientsViewMock.Verify(view => view.SetInput01Data(data.Input01Data), "Input01 data expected be set on button click.");
}

Categories

Resources