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;
}
}
Related
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;
}
I am working on Xamrin Form right now. I have problem with clear data of ViewModel.
When I logout and login with different user, it shows me data of previous user because the value of UserProfileViewModel doesn't get clear.
When user logout, I want to clear user data from UserProfileViewModel class file. Currently I do this manually when user click on logout. I want any default method like dispose to clear all class member.
I have tried to inherit IDisposable interface with this.Dispose(); but that also didn't work.
I have also tried with default constructor as following but it throws error of
`System.TypeInitializationException`
on this line in app.xaml.cs: public static ViewModelLocator Locator => _locator ?? (_locator = new ViewModelLocator());
public UserProfileViewModel()
{
//initialize all class member
}
In given code, you can see that on Logout call, I call method
`ClearProfileData` of `UserProfileViewModel`
which set default(clear)
data. It is manually. I want to clear data when user logout.
View Model Logout Page
[ImplementPropertyChanged]
public class LogoutViewModel : ViewModelBase
{
public LogoutViewModel(INavigationService nService, CurrentUserContext uContext, INotificationService inService)
{
//initialize all class member
private void Logout()
{
//call method of UserProfileViewModel
App.Locator.UserProfile.ClearProfileData();
//code for logout
}
}
}
User Profile View Model
[ImplementPropertyChanged]
public class UserProfileViewModel : ViewModelBase
{
public UserProfileViewModel(INavigationService nService, CurrentUserContext uContext, INotificationService inService)
{
//initialize all class member
}
//Is there any other way to clear the data rather manually?
public void ClearProfileData()
{
FirstName = LastName = UserName = string.Empty;
}
}
ViewModel Locator
public class ViewModelLocator
{
static ViewModelLocator()
{
MySol.Default.Register<UserProfileViewModel>();
}
public UserProfileViewModel UserProfile => ServiceLocator.Current.GetInstance<UserProfileViewModel>();
}
Firstly there is no need to cleanup these kinds of primitive data types, the gc will do that for you.
However if you use Messages or any other Strong Reference for that matter you WILL have to Unsubscribe from them otherwise your viewmodal will hang around in memory and will never go out of scope
The garbage collector cannot collect an object in use by an
application while the application's code can reach that object. The
application is said to have a strong reference to the object.
With Xamarin it really depends how you are coupling your View to Viewmodals to determine which approach you might take to cleanup your viewmodals.
As it turns out MVVM Light ViewModelBase implements an ICleanup interface which has an overridable Cleanup method for you.
ViewModelBase.Cleanup Method
To cleanup additional resources, override this method, clean up and
then call base.Cleanup().
public virtual void Cleanup()
{
// clean up your subs and stuff here
MessengerInstance.Unregister(this);
}
Now your just left with where to call ViewModelBase.Cleanup
You can just call it when your View Closes, if you get a reference to the DataContext (I.e ViewModalBase) on the DataContextChanged Event
Or you can wire up a BaseView that plumbs this for you, or you can implement your own NagigationService which calls Cleanup on Pop. It really does depend on who is creating your views and viewmodels and how you are coupling them
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
I have a user control of list of Patients which I use in other Views. However when I choose one of the Patients, the selection is propagated to all the views containing an instance of the user control. How can I make each view instantiate a new instance of the user control for each view?
I am using c#
Guessing from what you stated, I'd assume that you returning a static instance of you PatientViewModel from you locator. To solve this make sure that when the property is called a new instance of the view model is generated.
Edit: Locator with different instantiation methods
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
//if (ViewModelBase.IsInDesignModeStatic)
//{
// // Create design time view services and models
// SimpleIoc.Default.Register<IDataService, DesignDataService>();
//}
//else
//{
// // Create run time view services and models
// SimpleIoc.Default.Register<IDataService, DataService>();
//}
SimpleIoc.Default.Register<MainViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public MainViewModel StaticMain
{
get
{
return _staticMain ?? (_staticMain = new MainViewModel());
}
}
private static MainViewModel _staticMain;
public MainViewModel NewMain
{
get
{
return new MainViewModel();
}
}
public MainViewModel NewIocMain
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>(Guid.NewGuid().ToString());
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
The Main property uses Lauent's SimpleIoc container to create an instance. One of the properties of this container is, that it treats every type as singleton. Thus if you use this VM generation method you will share the VM throughout the views.
The StaticMain property does much the same, but instead of using Laurent's container it holds a static instance of the VM which is also shared between the views.
The NewMain property creates a new VM upon every call, therefore, the VM is not shared between the views.
The NewIocMain property also creates a new VM upon every call and the VM is, therefore, not shared between the views. However, the SimpleIoc container holds a reference to the instance created. It does not release this instance automatically and you have to call SimpleIoc.Default.Unregister(key) with the key you used for creation (the Guid) to remove the instance from the container once you no longer need it.
Instead of using the SimpleIoc you obviously can opt to use another IOC Container - such as Unity for example - that allows you greater control how your instances are created and how long they live. Barring this, I'd opt for the NewMain approach given yor case.
We have an old Silverlight UserControl + WCF component in our framework and we would like to increase the reusability of this feature. The component should work with basic functionality by default, but we would like to extend it based on the current project (without modifying the original, so more of this control can appear in the full system with different functionality).
So we made a plan, where everything looks great, except one thing. Here is a short summary:
Silverlight UserControl can be extended and manipulated via ContentPresenter at the UI and ViewModel inheritance, events and messaging in the client logic.
Back-end business logic can be manipulated with module loading.
This gonna be okay I think. For example you can disable/remove fields from the UI with overriden ViewModel properties, and at the back-end you can avoid some action with custom modules.
The interesting part is when you add new fields via the ContentPresenter. Ok, you add new properties to the inherited ViewModel, then you can bind to them. You have the additional data. When you save base data, you know it's succeeded, then you can start saving your additional data (additional data can be anything, in a different table at back-end for example). Fine, we extended our UserControl and the back-end logic and the original userControl still doesn't know anything about our extension.
But we lost transaction. For example we can save base data, but additional data saving throws an exception, we have the updated base data but nothing in the additional table. We really doesn't want this possibility, so I came up with this idea:
One WCF call should wait for the other at the back-end, and if both arrived, we can begin cross thread communication between them, and of course, we can handle the base and the additional data in the same transaction, and the base component still doesn't know anything about the other (it just provide a feature to do something with it, but it doesn't know who gonna do it).
I made a very simplified proof of concept solution, this is the output:
1 send begins
Press return to send the second piece
2 send begins
2 send completed, returned: 1
1 send completed, returned: 2
Service
namespace MyService
{
[ServiceContract]
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service1
{
protected bool _sameArrived;
protected Piece _same;
[OperationContract]
public Piece SendPiece(Piece piece)
{
_sameArrived = false;
Mediator.Instance.WaitFor(piece, sameArrived);
while (!_sameArrived)
{
Thread.Sleep(100);
}
return _same;
}
protected void sameArrived(Piece piece)
{
_same = piece;
_sameArrived = true;
}
}
}
Piece (entity)
namespace MyService
{
[DataContract]
public class Piece
{
[DataMember]
public long ID { get; set; }
[DataMember]
public string SameIdentifier { get; set; }
}
}
Mediator
namespace MyService
{
public sealed class Mediator
{
private static Mediator _instance;
private static object syncRoot = new Object();
private List<Tuple<Piece, Action<Piece>>> _waitsFor;
private Mediator()
{
_waitsFor = new List<Tuple<Piece, Action<Piece>>>();
}
public static Mediator Instance
{
get
{
if (_instance == null)
{
lock (syncRoot)
{
_instance = new Mediator();
}
}
return _instance;
}
}
public void WaitFor(Piece piece, Action<Piece> callback)
{
lock (_waitsFor)
{
var waiter = _waitsFor.Where(i => i.Item1.SameIdentifier == piece.SameIdentifier).FirstOrDefault();
if (waiter != null)
{
_waitsFor.Remove(waiter);
waiter.Item2(piece);
callback(waiter.Item1);
}
else
{
_waitsFor.Add(new Tuple<Piece, Action<Piece>>(piece, callback));
}
}
}
}
}
And the client side code
namespace MyClient
{
class Program
{
static void Main(string[] args)
{
Client c1 = new Client(new Piece()
{
ID = 1,
SameIdentifier = "customIdentifier"
});
Client c2 = new Client(new Piece()
{
ID = 2,
SameIdentifier = "customIdentifier"
});
c1.SendPiece();
Console.WriteLine("Press return to send the second piece");
Console.ReadLine();
c2.SendPiece();
Console.ReadLine();
}
}
class Client
{
protected Piece _piece;
protected Service1Client _service;
public Client(Piece piece)
{
_piece = piece;
_service = new Service1Client();
}
public void SendPiece()
{
Console.WriteLine("{0} send begins", _piece.ID);
_service.BeginSendPiece(_piece, new AsyncCallback(sendPieceCallback), null);
}
protected void sendPieceCallback(IAsyncResult result)
{
Piece returnedPiece = _service.EndSendPiece(result);
Console.WriteLine("{0} send completed, returned: {1}", _piece.ID, returnedPiece.ID);
}
}
}
So is it a good idea to wait for another WCF call (which may or may not be invoked, so in a real example it would be more complex), and process them together with cross threading communication? Or not and I should look for another solution?
Thanks in advance,
negra
If you want to extend your application without changing any existing code, you can use MEF that is Microsoft Extensibility Framework.
For using MEF with silverlight see: http://development-guides.silverbaylabs.org/Video/Silverlight-MEF
I would not wait for 2 WCF calls from Silverlight, for the following reasons:
You are making your code more complex and less maintainable
You are storing business knowledge, that two services should be called together, in the client
I would call a single service that aggreagated the two services.
It doesn't feel like a great idea to me, to be honest. I think it would be neater if you could package up both "partial" requests in a single "full" request, and wait for that. Unfortunately I don't know the best way of doing that within WCF. It's possible that there's a generalized mechanism for this, but I don't know about it. Basically you'd need some loosely typed service layer where you could represent a generalized request and a generalized response, routing the requests appropriately in the server. You could then represent a collection of requests and responses easily.
That's the approach I'd look at, personally - but I don't know how neatly it will turn out in WCF.