How to avoid calling long operations from constructor - c#

I use MVVM and I have to create a ViewModel class that should load lots of data when the View is opened.
Basically, when I create the viewmodel, it should use the database and get the data.
I used this approach first:
public class MainViewModel
{
public MainViewModel()
{
//set some properties
Title = "Main view";
//collect the data
StartLongOperation();
}
private void StartLongOperation()
{
Thread t=new Thread(...);
t.start();
}
}
It works and loads the data without blocking the UI thread.
Later I found this guideline about how to use constructor, and it does not recommend starting long operation from constructor.
√ DO minimal work in the constructor.
Constructors should not do much work other than capture the
constructor parameters. The cost of any other processing should be
delayed until required.
In my case, the data is required on opening the view.
My first idea was to use an event.
How should I avoid calling the long operation from construcTor? What is the best practice?

Miguel Castro has talked about a solution to this in one of his great Pluralsight courses. He binds to a property in the viewmodel called ViewLoaded which will obviously get bound when the view loads, this in turn will call your long running method.
So this goes in the view (Or a base class for all views to help with re-use):
public ViewBase()
{
// Programmatically bind the view-model's ViewLoaded property to the view's ViewLoaded property.
BindingOperations.SetBinding(this, ViewLoadedProperty, new Binding("ViewLoaded"));
}
public static readonly DependencyProperty ViewLoadedProperty =
DependencyProperty.Register("ViewLoaded", typeof(object), typeof(UserControlViewBase),
new PropertyMetadata(null));
And this is the ViewModel base class code:
public object ViewLoaded
{
get
{
OnViewLoaded();
return null;
}
}
protected virtual void OnViewLoaded() { }
Simply override the OnViewLoaded() method in your ViewModel and call the long running method from there.

Maybe use a factory pattern to avoid having the MainViewModel around but not populated.
public class VMFactory
{
public async Task<MainViewModel> GetVM()
{
MainViewModel vm = new MainViewModel();
await vm.LongOperation();
return vm;
}
}
public class MainViewModel
{
public MainViewModel()
{
//set some properties
Title = "Main view";
}
public async Task LongOperation()
{
(...)
}
}
or perhapse better. move the long running method out of the MainViewModel to a repository or service
public class VMRepository
{
public async Task LongOperation()
{
(...)
}
public async Task<MainViewModel> GetVM()
{
MainViewModel vm = new MainViewModel();
vm.DataWhichTakesAlongTime = await LongOperation();
return vm;
}
}
public class MainViewModel
{
public MainViewModel()
{
//set some properties
Title = "Main view";
}
public object DataWhichTakesAlongTime { get; set; }
}
To be honest though it sounds from the conversations around this question that you are simply using the constructor as a convenient trigger for a 'LoadDataNow' command and really you should add an ICommand, bind it to something in the view (Loaded) add loading spinners and completed events etc etc
Controversially I might also suggest you add a Controller Class to instantiate the repository view and vm and call the 'LoadData' method on the view. Not very MVVM I know but essentially doing the same stuff your IoC container does without having to jump through the hoops of configuration

Avoiding calling it is simple, just split it into 2 methods; The constructor and a GetData method you call when you open the view or after you set the data context.
The why is just about managing expectation. If you hadn't written the code and were writing a new view for someone else's view model, would you expect the constructor to start accessing a database? or would you expect it just to construct a view model and you need to make a second call to initiate getting the data?

Use your view lifecycle to execute this method. You can use Tasks to simplify the execution and you can bind to other properties to show progress. Example shown using a Windows Store App view.
ViewModel:
public class MainViewModel
{
public MainViewModel()
{
this.Title = "Main view";
}
public async Task StartLongOperationAsync()
{
this.IsLoading = true;
await Task.Run(() =>
{
//do work here
});
this.IsLoading = false;
}
}
And on the View:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
await ((MainViewModel)this.DataContext).StartLongOperationAsync();
}

I don't know maybe it's wrong but sometimes i make(if i need retrun parameters)
public class MainViewModel
{
public MainViewModel()
{
//set some properties
Title = "Main view";
}
public static string GetMainViewModelString()
{
var mainViewModel = new MainViewModel();
return mainViewModel.GetString();
}
public string GetString()
{
/*your code*/
}
}
and then call
var stringData = MainViewModel.GetMainViewModelString();
but when it need i call some operation from constructor

Related

Run Task after View loaded without ViewModel class in Prism

Hi I want a task to run immediately after the view is opened without need for ViewModel class
This is how I do it right now
public class ContributorsViewModel : DemoViewModelBase<Model>
{
public ContributorsViewModel()
{
Task.Run(() => DataList = new DataService().GetDataList());
}
}
DemoViewModelBase:
public class DemoViewModelBase<T> : BindableBase
{
private IList<T> _dataList;
public IList<T> DataList
{
get => _dataList;
set => SetProperty(ref _dataList, value);
}
}
Bootstrapper:
containerRegistry.RegisterForNavigation<ContributorsView>();
I do not know how to do this without a viewmodel
You can do anything you want in code-behind, for example subscribe to the Loaded event:
internal class ContributorsView
{
public ContributorsView()
{
InitializeComponents();
Loaded += async (s, e) => await Task.Run( ...whatever... );
}
}
But keep in mind:
you won't be able to test this
you do not get the benefit of injected dependencies
without [...] ViewModel class
This seems to be a really bad idea(*), because what you want to do is essentially what view models are there for.
(*) unless you have some architectural that you didn't reveal

How to create view-model of prism when needed?

I really made a search for this topic and did not find anything, and because of that, I am asking the question here.
I have a WPF application with Prism installed.
I have wired the view-model with the view automatically by name convention
<UserControl x:Class="Views.ViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
and the model in the 'Model' like this
public class ViewAViewModel {
public ViewAViewModel () {
// time-resource consuming operations
}
}
the automatic binding work perfectly without a problem and the view and its corresponding view-model is matching, but the problem here.
I have a lot of those views say (50) and for every one of them, the view-model will be created with constructor exhausting the processes. This will make the startup of the application longer and also it will create a lot of view-models objects and put them in the RAM without being sure that they will be used at all.
What I need is to create the view-model class when the view is activated (I mean when the view is navigated to). Is this possible and if yes how?
Update
here is how I register the view with the Module, this is causing all the views to be created when the startup of the module.
public class Module1 : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("region1", typeof(View1));
regionManager.RegisterViewWithRegion("region1", typeof(View2));
is there any way to delay the creating of the views, until the navigation request come?
You could use navigation, for each view.
Or you must create an interfaces for your view and view model.
An example:
public interface IMyView
{
IMyViewModel ViewModel { get; set; }
}
public interface IMyViewModel
{
}
In the module or app.cs, in the method RegisterTypes you should register these.
containerRegistry.Register<IMyView, MyView>();
containerRegistry.Register<IMyViewModel, MyViewModel>();
You must implement IMyView interface in your MyView.cs class.
public partial class MyView : UserControl, IMyView
{
public MyView(IMyViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
}
public IMyViewModel ViewModel
{
get => DataContext as IMyViewModel;
set => DataContext = value;
}
}
After you could use it:
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
var firstView = containerProvider.Resolve<IMyView>();
regionManager.AddToRegion(RegionNames.MainRegion, firstView);
}
In such case you shouldn't use ViewModelLocator.AutoWireViewModel in your view.

What is the best way to populate a data-bound property from an async method?

I have a simple requirement I am trying to achieve. Basically I have a view that gets populates with a list of businesses. The property for the list of businesses is embodied in my viewmodel class which in turn is bound to the view. This is a simple MVC application with a list of business.
However, the issue I have is that I derive the list of business for another class which is a dependency to the view model, and its basically similar to a repository which I call BusinessService. Busy service is comprised of async methods and this is the dilemma I have, when the call is made from the Ctor of the viewModel or the getter of the property, my application hangs. The call is to a EF database asynchrounous too within the businessservice and am not sure what is the correct approach for this. Please see code below:
ViewModel:
#region Ctor
public BusinessListViewModel(IBusinessService businessService, IStringBuilder builder)
{
_businessService = businessService;
_builder = builder;
InitBusinesses().Wait(); //OPTION 1
}
#endregion
#region Properties
public IEnumerable<BusinessViewModel> _businesses;
public IEnumerable<BusinessViewModel> Businesses
{
get
{
if (_businesses == null)
{
InitBusinesses().Wait(); //OPTION 2
}
return _businesses;
}
set => _businesses = value;
}
private async Task InitBusinesses()
{
var response = await _businessService.Get();
Businesses = response.IsSuccessful
? response.Data.Select(p => new BusinessViewModel(_builder, p))
: new List<BusinessViewModel>();
}
BUSINESS SERVICE:
#region Service Methods
public async Task<Response<IEnumerable<Models.Business>>> Get()
{
var data = await Db.Businesses.ToListAsync();
return new Response<IEnumerable<Models.Business>>
{
IsSuccessful = true,
Message = "Successful",
Data = Mapper.Map<List<Models.Business>>(data)
};
}
Please may you advise the best pattern and the correct way to do this, I already know this is wrong> Thank you
I wrote an article on the subject.
When the UI framework asks your code to display something, it must be displayed immediately (synchronously). ViewModel constructors and data-bound properties should be synchronous and immediate. Doing network I/O is simply not an option; even if you got it working (which is possible), all that would do is block your UI thread, degrading your user experience.
A more proper solution is to synchronously initialize into a loading state ("Loading..." message, spinner, whatever) and also start the asynchronous operation. Then, when the operation completes, update the UI with the actual data.
You should consider using a factory method that returns a Task
private BusinessListViewModel(IBusinessService businessService, IStringBuilder builder)
{
_businessService = businessService;
_builder = builder;
}
public static async Task<BusinessListViewModel> Create(IBusinessService businessService, IStringBuilder builder)
{
var instance = new BusinessListViewModel(businessService, builder)
await InitBusiness();
return instance;
}

Accessing a method in an existing instance of a class from AppDelegate

I need to pass some data that I grab when the app comes back into the foreground, I have managed to trigger the method but I can't figure out how to trigger it in the existing instance of my ViewController rather than making a new instance.
Map.cs
public delegate void beginRefreshMapLine(ReturnRouteTaken returnRouteTaken);
public void updateRouteList(ReturnRouteTaken returnRouteData)
{
coordList = new List<CLLocationCoordinate2D>();
foreach(GPSData point in returnRouteData.GPSData)
{
coordList.Add(new CLLocationCoordinate2D { Latitude = double.Parse(point.Lat), Longitude = double.Parse(point.Lng) });
updateMap(this, new EventArgs());
}
}
this is the method I need to trigger in the current instance from AppDelegate.cs
AppDelegate.cs
if (GlobalVar.BoolForKey("trackMe"))
{
ReturnRouteTaken returnRouteData = webtools.GetRouteTaken(new ReturnRouteTaken() { TestDriveID = GlobalVar.IntForKey("routeTrackedID") });
if (returnRouteData.GPSData.Count > 0)
{
}
}
Here is where I am stuck, I have tried looking into delegates and invoking the method that way but I cannot get my head around how to implement it. Any help would be appreciated
I flagged this as a possible dupe, but that thread is in Obj-C, however the same concept can easily be applied using Xamarin.iOS.
Just create a Singleton class with an array or List of UIViewControllers as a property in that class and every time you instantiate a new ViewController, add it to the array orList, but also make sure you remove a view controller from the array or List when the view controller is disposed.
e.g. your singleton could look like:
public class ViewControllerHolder
{
// make constructor private to force use of Instance property
// to create and get the instance.
private ViewControllerHolder()
{
}
private static ViewControllerHolder _instance;
public static ViewControllerHolder Instance
{
get
{
if (_instance == null)
{
_instance = new ViewControllerHolder();
_instance.Controllers = new List<UIViewController>();
}
return _instance;
}
}
public List<UIViewController> Controllers { get; private set; }
}
And then you can always get access to your List of view controllers with ViewControllerHolder.Instance.Controllers and perform any add or remove operations on it.
And if you are really only interested in the one view controller, then just add that one to the List when instantiated, but do remove it when the view controller is no longer needed so you don't try to access a disposed view controller and also so that the view controller can be garbage collected when it is no longer in use.
Creating a singleton array that holds all the living UIViewControllers works, personally I like to keep things decoupled as much as I can and do not like holding and maintaining a list of objects for no real reason...
You can pass data around via:
Selector
NoticationCenter
In any UIViewController that you need to "talk" to, you can subscribe to notifications and/or register Selectors.
In your UIViewController register for which Notifications you wish to receive...
public override void ViewDidLoad()
{
base.ViewDidLoad();
NSNotificationCenter.DefaultCenter.AddObserver(this, new Selector(Const.StartRefresh), new NSString(Const.StartRefresh), null);
}
Still in your UIViewController, implement the selector that the notification center will perform a send_msg to:
[Export(Const.StartRefresh)]
void LocalStartRefresh(NSNotification notification)
{
if (notification.Name == Const.StartRefresh)
Console.WriteLine("StartRefresh from NotificationCenter:" + notification.Object);
}
In your UIApplicationDelegate, use the notification center to publish a new NSNotification to every active UIViewController that has subscribed:
public override void WillEnterForeground(UIApplication application)
{
NSNotificationCenter.DefaultCenter.PostNotificationName(Const.StartRefresh, new NSString("some custom data"));
}
Or, skip notifications and directly invoke the Selector:
In your UIViewController, implement the selector/method to call:
[Export(Const.StopRefresh)]
void LocalStopRefresh()
{
Console.WriteLine("StopRefresh from Selector");
}
In your UIApplicationDelegate, send an action to all instanced view controller instances that accept this Selector:
public override void DidEnterBackground(UIApplication application)
{
var vc = UIApplication.SharedApplication?.KeyWindow?.RootViewController;
while (vc != null)
{
if (vc.RespondsToSelector(new Selector(Const.StopRefresh)))
UIApplication.SharedApplication.SendAction(new Selector(Const.StopRefresh), vc, this, new UIEvent());
vc = vc.PresentedViewController;
}
}

Pass parameter via ICommand

The following works if I do not pass anything from the View.
View.cs
ViewModel.ReloadCommand.Execute(null);
ViewModel.cs
public ICommand ReloadCommand
{
get
{
return new MvxCommand(async () =>
{
await RefreshStudentList();
});
}
}
However I need to pass a parameter, I wonder how could I do that?
ViewModel.ReloadCommand.Execute(xxx);
ViewModel.cs
public ICommand ReloadCommand
{
get
{
return new MvxCommand(async () =>
{
await RefreshStudentList(xxx);
});
}
}
To do async operation MvvmCross also has a MvxAsyncCommand which also can take a parameter as the regular MvxCommand.
It looks something like this:
public ICommand ReloadCommand
{
return new MvxAsyncCommand(DoAsyncStuff);
}
private Task DoAsyncStuff(MyType type)
{
}
Any command can be executed with a parameter like:
ViewModel.ReloadCommand.Execute(myParameter);
I'm not familiar with MvvmCross, but from what I can tell, it would be something like this:
public ICommand ReloadCommand
{
get
{
return new MvxCommand<XXXType>(async (xxx) =>
{
await RefreshRoutesList(xxx);
});
}
}
Instead of doing that, try initialize your ViewModel on your View first. Then, based on your code, the ICommand does only RefreshRoutesList function, so I will access the RefreshRoutesList directly from the View. To make the naming clear, I will use MyView.cs and MyViewModel.cs
MyView.cs
MyViewModel vm;
.
.
protected override void OnCreate(Bundle bundle)
{
.... //other stuff
vm = ViewModel as MyViewModel;
}
After doing that, you could call your function anywhere in your view using vm variable, i.e.
await vm.RefreshRoutesList(aParameter);
Hope this can help.
Not quite the answer you might be looking for but...
My understanding of Mvvm is that it reflects the state of the View and reacts to Commands from the View. Your parameter can be treated as State and as such should have its own Property on the ViewModel that it would be bound to. Thus your Command would not have to pass a parameter. This would also further de-couple the ViewModel from the View's implementation.

Categories