Why DisplayAlert does not work in constructor? - xamarin.forms

I have some problems in my Xamarin.Forms app related to invoking async method in the page constructor so when I test something trying to figure out the reason I just realized DisplayAlert method does not even work in the page constructor so I am wondering why is that happening?
Here is my code:
public MainPage ()
{
InitializeComponent ();
DisplayAlert("An alert", "Why I don't show up?", "Ok");
}
and I also tried to call async method that has DisplayAlert method but didn't work too, here is the code:
public MainPage ()
{
InitializeComponent ();
Async_Function_Has_DisplayAlert();
}
async void Async_Function_Has_DisplayAlert()
{
// I tried both and neither of them worked
await DisplayAlert("An alert", "Why I don't show up?", "Ok");
await Task.Run(()=> DisplayAlert("An alert", "Why I don't show up?", "Ok"));
}
So can someone explain why that is happening please?

Normally, you should not call an awaitable method like DisplayAlert() from the constructor.
What you can do is have a method that returns void (still not best practice) and call that method from your constructor.
Tweaking my recommendation after trying it out.
I used Device.Timer to delay the alert.
I think some components have not finished loading (in this case, the DisplayAlert) before trying to call it.
public MainPage()
{
InitializeComponent();
Device.StartTimer(TimeSpan.FromSeconds(4), () =>
{
ShowMessage();
return false; // True = Repeat again, False = Stop the timer
});
}
public async void ShowMessage()
{
await DisplayAlert("Alert", "I show here", "OK");
}

There seems to be a misconception on what is happening in the constructor.
The constructor simply creates a new Page class.
CustomMainPage mainpage = new CustomMainPage();
(App.Current as App).MainPage = new NavigationPage(mainpage);
So before we add the mainpage class to the NavigationPage, all that has happened is that the CustomMainPage class was initialized and is ready to be inserted into an appropriate container.
However after creating the new page, there is no actual UI on the screen, yet. For instance, the mainpage object wouldn't have width or height set, no layout has been done, etc...
If you run a UI related task, such as presenting an Alert, there isn't simply any foundation for it there, which could do anything resonable.
Of course you could already set members of the mainpage, such as labels or buttons to certain values, colors, styles or whatever you want, from within the constructor, but these wouldn't do anything at that point of time.
All of those values will be taken into account when the page is being layouted and presented but none of that will happen in the constructor.
However, back to your problem: You seemingly want to inform the user that something has gone wrong during the initialization.
I see two ways of adressing that issue:
Check the preconditions on the page or within the code before initializing your view and present the Alert from the page or class, which is initializing your page.
create a private variable in your page class, which you will set from within your page constructor if something goes wrong. This could be a simple bool flag, a string containing an error message, an enum or whatever suits your needs. Then override the OnAppearing() method, check that flag you set earlier and call DisplayAlert depending on the flag's value.
If you want any interactivity on your page, then you should consider Jason's comment to your question and implement it within OnAppearing, because this method will be called once your page has been fully layouted and is being presented on your screen.

Sample code for Jason's recommendation
public async void ShowMessage()
{
await DisplayAlert("Alert", "I show here", "OK");
}
protected override void OnAppearing()
{
ShowMessage();
}

Related

Re-instantiate a singleton with Prism in Xamarin Forms

How can I dispose and re-instantiate a singleton with Prism/DryIoC in Xamarin Forms?
I'm working with Azure Mobile Apps for offline data. Occasionally, I need to delete the local sqlite database and re-initialize it. Unfortunately the MobileServiceClient occasionally holds the db connection open and there's no method exposed to close it. The suggested solution (https://github.com/Azure/azure-mobile-apps-net-client/issues/379) is to dispose of MobileServiceClient. Only problem is that is registered with DryIoC as a singleton.
I'm not overly familiar with DryIoC, or Prism and Forms for that matter... But for the life of me, I can't see a way to do this.
I did cook up a pretty elaborate scheme that almost worked.
In my ViewModel method, when I needed the db freed up, I fired off an event -
_eventAggregator.GetEvent<RegisterDatabaseEvent>().Publish(false);
Then in App.xaml.cs, I wired up a listener and a handler like so -
_eventAggregator.GetEvent<RegisterDatabaseEvent>().Subscribe(OnRegisterDatabaseEventPublished);
private void OnRegisterDatabaseEventPublished()
{
Container.GetContainer().Unregister<IAppMobileClient>();
Container.GetContainer().Unregister<IMobileServiceClient>();
Container.GetContainer().Register<IMobileServiceClient, AppMobileClient>(new SingletonReuse());
Container.GetContainer().Register<IAppMobileClient, AppMobileClient>(new SingletonReuse());
_eventAggregator.GetEvent<RegisterDatabaseCompletedEvent>().Publish(register);
}
Lastly, back in the ViewModel constructor, I had a final listener that handled the event coming back from App.xaml and finished processing.
_eventAggregator.GetEvent<RegisterDatabaseCompletedEvent>().Subscribe(OnRegisterDatabaseCompletedEventPublished);
So the amazing thing is that this worked. The database was able to be deleted and all was good. But then I navigated to a different page and BOOM. DryIoC said it couldn't wire up the ViewModel for that page. I assume the unregister/register jacked up DryIoC for all injection... So how can I accomplish what needs to be done?
FINAL SOLUTION
Thanks so much to dadhi for taking the time to help. You are certainly a class act and I'm now considering using DryIoC elsewhere.
For anyone who stumbles on this, I'm posting the final solution below. I'll be as verbose as I can to avoid any confusion.
First, in my App.xaml.cs, I added a method for registering my database.
public void RegisterDatabase(IContainer container)
{
container.RegisterMany<AppMobileClient>(Reuse.Singleton,
setup: Setup.With(asResolutionCall: true),
ifAlreadyRegistered: IfAlreadyRegistered.Replace,
serviceTypeCondition: type =>
type == typeof(IMobileServiceClient) || type == typeof(IAppMobileClient));
}
I simply add a call to that method in RegisterTypes in place of registering the types in there directly.
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.GetContainer().Rules.WithoutEagerCachingSingletonForFasterAccess();
...
RegisterDatabase(containerRegistry.GetContainer());
...
}
Note also the added rule for eager caching, per dadhi.
Later on when I need to release the database in the ViewModel... I kick things off by resetting my local db variable and sending an event to App.xaml.cs
_client = null;
_eventAggregator.GetEvent<RegisterDatabaseEvent>().Publish(true);
In App.xaml.cs, I have subscribed to that event and tied it to the following method.
private void OnRegisterDatabaseEventPublished()
{
RegisterDatabase(Container.GetContainer());
_eventAggregator.GetEvent<RegisterDatabaseCompletedEvent>().Publish(register);
}
Here I just call RegisterMany again, exactly the same as I do when the app starts up. No need to unregister anything. With the setup and ifAlreadyRegistered arguments (thanks, dadhi!), DryIoC allows the object to be replaced. Then I raise an event back to the VM letting it know the database has been released.
Finally, back in the ViewModel, I'm listening for the completed event. The handler for that event updates the local copy of the object like so.
_client = ((PrismApplication)App.Current).Container.Resolve<IAppMobileClient>();
And then I can work with the new object, as needed. This is key. Without setting _client to null above and resolving it again here, I actually ended up with 2 copies of the object and calls to methods were being hit 2x.
Hope that helps someone else looking to release their Azure Mobile Apps database!
I am not sure how exactly XF handles these things.
But in DryIoc in order for service to be fully deleted or replaced it need to be registered with setup: Setup.With(asResolutionCall: true). Read here for more details: https://bitbucket.org/dadhi/dryioc/wiki/UnregisterAndResolutionCache#markdown-header-unregister-and-resolution-cache
Update
Here are two options and considerations that work in pure DryIoc and may not work XF. But it probably may help with solution.
public class Foo
{
public IBar Bar { get; private set; }
public Foo(IBar bar) { Bar = bar; }
}
public interface IBar {}
public class Bar : IBar {}
public class Bar2 : IBar { }
[Test]
public void Replace_singleton_dependency_with_asResolutionCall()
{
var c = new Container(rules => rules.WithoutEagerCachingSingletonForFasterAccess());
c.Register<Foo>();
//c.Register<Foo>(Reuse.Singleton); // !!! If the consumer of replaced dependency is singleton, it won't work
// cause the consumer singleton should be replaced too
c.Register<IBar, Bar>(Reuse.Singleton,
setup: Setup.With(asResolutionCall: true)); // required
var foo = c.Resolve<Foo>();
Assert.IsInstanceOf<Bar>(foo.Bar);
c.Register<IBar, Bar2>(Reuse.Singleton,
setup: Setup.With(asResolutionCall: true), // required
ifAlreadyRegistered: IfAlreadyRegistered.Replace); // required
var foo2 = c.Resolve<Foo>();
Assert.IsInstanceOf<Bar2>(foo2.Bar);
}
[Test]
public void Replace_singleton_dependency_with_UseInstance()
{
var c = new Container();
c.Register<Foo>();
//c.Register<Foo>(Reuse.Singleton); // !!! If the consumer of replaced dependency is singleton, it won't work
// cause the consumer singleton should be replaced too
c.UseInstance<IBar>(new Bar());
var foo = c.Resolve<Foo>();
Assert.IsInstanceOf<Bar>(foo.Bar);
c.UseInstance<IBar>(new Bar2());
var foo2 = c.Resolve<Foo>();
Assert.IsInstanceOf<Bar2>(foo2.Bar);
}

MVVMLight Messenger.Unregister - should I unregister and how to do it?

I've looked at 'Messenger and references' discussion, but I'm writing a new topic, because my issue is not technical, and I don't want to offtop there.
I've encountered a doubt - Have I to code cleanup()/RequestCleanup() method implementation to unregister previously registered Messenger in my viewmodel class? I'm afraid of memory leaks in the future.
I think I've found the documentation not to be enough bright for me.
Description of Messenger.Register is: '... Registering a recipient does not create a hard reference to it, so if this recipient is deleted, no memory leak is caused.'
1) Is this mean that I don't have to take care of it and implement-develop following solutions?
On the other hand, we can find in the code of GalaSoft.MvvmLight.ViewModelBase abstract class the short info about the Cleanup() method:
//
// Summary:
// Unregisters this instance from the Messenger class.
// To cleanup additional resources, override this method, clean up and then
// call base.Cleanup().
public virtual void Cleanup();
so 2) Is only invoking a Cleanup enough to unregister class-instance out of the Messenger?
3) Or maybe I have to invoke Messenger.Default.Unregister(this); in the body of a Cleanup method?
4) In the Unregister(Object) doc we read 'Unregisters a messager recipient completely' - what does the 'completely' mean?
I'm very sorry if my post seems to have out of the context quotes, I wanted to point out what I'm more interested in.
EDIT 1:
Hello Joel, thanks for reply. I've got several questions:
1) I have used your code. There's defined override void Cleanup() in CustomerMasterViewModel. Where to call it? Should I declare destructor in this case or maybe the ViewModelBase has an automatic mechanism for invoking the Cleanup()?
2) I have in my project another base class from a different toolkit, so my VMs cannot derive from both at the same time. How to organise your code to get the same effect by implementing only ICleanup interface?
public class CustomerMasterViewModel : SomeBaseClass, ICleanup
{
public CustomerMasterViewModel()
{
Messenger.Default.Register<Message>(this, this.MessageReceived);
}
#region messages
private void MessageReceived(Message obj)
{
//do something
}
#endregion
#region helper methods
public override void Cleanup()
{
//base.Cleanup();//there's no implementaction in an interface
ViewModelLocator.Cleanup();
}
#endregion
}
You have to invoke the Cleanup() method in GalaSoft.MvvmLight.ViewModelBase on each of you view models you wan't to dispose don't need any longer.
Example:
Let say your application has a tab control with different tabs. Each of your tabs displays a UserControl which has a dedicated ViewModel. The user has the ability to close a tabs which causes the underlining ViewModel to become obsolete.
Want you want to do now is to clean up the ViewModel calling the Cleanup() method in GalaSoft.MvvmLight.ViewModelBase. This will unregister ALL registered messages. The GarbageCollector will take care of you viewmodel if there are no other references.
Assuming you use the ViewModelLocator which also comes with the MVVM Light Framework you're not done yet because at least the ViewModelLocator itself has a reference to your viewmodel! Therefore the Garbage Collector can't finalize your viewmodel.
But it also has another side effect. When the user reopens the tab (Lets say the user is able to do so) the UserControl is loaded again and the ViewModelLocator will give you the same ViewModel instance. The only difference is that there are not registered messages because you cleaned them by calling the CleanUp() method.
What you need is a new instance of your ViewModel. To achieve this you have to clean up your ViewModelLocator as well!
You have to unregister them (Unregister<CustomerMasterViewModel>()) one by one or simply call Reset() which will unregister all viewmodels.
Then there should be no other reference to you viewmodel and the GarbageCollector can finally take care about it.
Here is an example to do so:
ViewModelLocator:
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<CustomerMasterViewModel>();
}
public CustomerMasterViewModel CustomerMasterViewModel
{
get
{
return ServiceLocator.Current.GetInstance<CustomerMasterViewModel>();
}
}
public static void Cleanup()
{
SimpleIoc.Default.Reset();
//Don't forget to register them if the user attempts to open the new.
//The viewmodel initialization is lazy by default so this comes at no costs.
SimpleIoc.Default.Register<CustomerMasterViewModel>();
}
}
ViewModel
public class CustomerMasterViewModel : ViewModelBase
{
public CustomerMasterViewModel()
{
Messenger.Default.Register<Message>(this, this.MessageReceived);
}
#region messages
private void MessageReceived(Message obj)
{
//do something
}
#endregion
#region helper methods
public override void Cleanup()
{
base.Cleanup();
ViewModelLocator.Cleanup();
}
#endregion
}
In Short:
1) As far as i understood clean up is necessary after you're done.
2) Yes, calling the Cleanup() method in GalaSoft.MvvmLight.ViewModelBase will unregister all messages for this viewmodel.
3) No, see above.
4) Completely means it will unregister ALL registered messages.

CaliburnMicro StackOverflowException when ActivateItem function is invoked

I have two VM - View (inherited from Screen) and Edit (inherited from Screen). View is used to display grid with data and Edit - add/edit new items into grid.
In my ShellViewModel I have the following code to activate View.
public void WorkstationView()
{
this.ActivateItem(ServiceLocator.Current.GetInstance<WorkstationViewModel>());
}
In WorkstationViewModel when user clicks on the Create button the following code is invoked
public void CreateAction()
{
EditableObject = new WorkstationDto();
TryClose(true);
}
And there is a listener to Deactivated event property, see code below (InitViewModels is invoked in ShellViewModel constructor).
private void InitViewModels()
{
#region Init
WorkstationViewModel = ServiceLocator.Current.GetInstance<WorkstationViewModel>();
WorkstationEditViewModel = ServiceLocator.Current.GetInstance<WorkstationEditViewModel>();
#endregion
#region Logic
WorkstationViewModel.Deactivated += (o, args) =>
{
if (WorkstationViewModel.EditableObject == null)
{
return;
}
WorkstationEditViewModel.EditableObject = WorkstationViewModel.EditableObject;
ActivateItem(WorkstationEditViewModel);
};
#endregion
}
The problem here is a StackOverflow exception when I close Edit view (see create action).
“Since the Conductor does not maintain a “screen collection,” the activation of each new item causes both the deactivation and close of the previously active item.” Caliburn.Micro documentation
If you are using Conductor<T>, then ActivateItem(WorkstationEditViewModel); inside of the Deactivated handler is implicitly re-triggering the deactivation of the previous viewmodel - giving you an infinite loop. Try changing your conductor to inherit from Conductor<IScreen>.Collection.OneActive instead. However, you will still have two deactivations: the one from the original TryClose operation, and a second one when you activate the new screen. Overriding DetermineNextItemToActivate can help you avoid that.

Messenger Class - Can AppBar Button us it to update screen

I have a button on my AppBar to "Sync All". This call a webservice for each provider and updates their data that is has been cached. Once updated I need to update the selected providers data on the screen, so how do I do this with mvvm-light.
1) When I try to access the data in the click even of the button I am not sure how to access the currently loaded view models so I can refresh the data behind them. Is there a way to access the view model from a click event and refresh the underlying data.
2) I am wondering that is what the messenger class is used for and if so are there any good examples I can look at to use this feature in when the user clicks the "Sync All" Button.
3) If the messenger class is not the way to do this and there is no way to access the current view-model, what other options do I have.
Any help will be appreciated.
You've got two ways of doing this:
Using messenger
Using standard events
Option 1 works pretty much regardless of your design and goes something like this:
User clicks your AppBar button, which calls a command
The command calls a service that, internally calls your webservice (the nice way) or the command just calls your webservice (the not so nice way). I'm expecting this method to be an async method.
Once you've got your new data you call Messenger.Default.Send() to broadcast to all listening viewmodels that they should refresh their data.
Code:
ViewModel:
public class ViewModel
{
public ViewModel()
{
Messenger.Default.Register<DataRefreshEvent>(this,ReceiveDataRefreshEvent);
}
private void ReceiveDataRefreshEvent(DataRefreshEvent obj)
{
//do what you need to do
}
}
Service:
public class Service
{
public async void RefreshData()
{
await _webService.RefreshDataAsync();
Messenger.Default.Send(new ReceiveDataRefreshEvent());
}
}
Option 2 works like option 1 except it expects you to have a central data service wrapping your webservice. This service would be injected into all of your viewmodels.
User clicks your AppBar button, which calls a command
The command calls a service that, internally calls your webservice (the nice way) or the command just calls your webservice (the not so nice way). I'm expecting this method to be an async method.
The service raises a standard event to let the subscribed viewmodels know that they need to refresh their data. So basically same as option 1 except that each viewmodel is actives subscribing to an event on the service rather than a Messaging event.
Code:
ViewModel:
public class ViewModel
{
public ViewModel(IService service)
{
service.DataChanged+=ReceiveDataRefreshedEvent;
}
private void ReceiveDataRefreshEvent(sender obj,EventArgs args)
{
//do what you need to do
}
}
Service:
public class Service:IService
{
public event EventHandler ReceiveDataRefreshedEvent;
public async void RefreshData()
{
await _webService.RefreshDataAsync();
if(ReceiveDataRefreshedEvent!=null)
ReceiveDataRefreshedEvent(this,EventArgs.Empty);
}
}
Option 1 is nice because you don't have to hand over a reference to the service to the ViewModel. It's nicely de-coupled.
If you are unsure about messaging use option 2.

Flex event will only fire once

In an AIR application, I have a private variable and a setter:
private var _saveResult
public function set saveResult( result:String ):void
{
_saveResult = result;
dispatchEvent( new resultUpdatedEvent( _saveResult ));
}
The first time that I set "saveResult" the event fires. But it will never fire again unless I restart the application.
If I change the setter to:
public function set saveResult( result:String ):void
{
_saveResult = result;
if ( result != null)
{
dispatchEvent( new resultUpdatedEvent( _saveResult ));
}
}
The problem goes away, I can set the variable many times and the event fires every time.
My question:
Am I doing something wrong here? If not, can anyone explain to me whats happening? If so, what SHOULD I be doing?
Thanks!
It looks like you're constructing your event incorrectly. The first parameter of an Event object should always be a string. So in this case you'd want to always use the same string so you could listen for the event. What does your resultUpdatedEvent class look like? You'll want it to look something like this:
package myEvents
{
import flash.events.Event;
public class PropertyChangeEvent extends Event
{
public static const PROPERTY_CHANGE:String = "propertyChange";
public var result:String = "";
// Public constructor.
public function PropertyChangeEvent (type:String,
result:String="") {
// Call the constructor of the superclass.
super(type);
// Set the new property.
this.result= result;
}
// Override the inherited clone() method.
override public function clone():Event {
return new PropertyChangeEvent(type, result);
}
}
}
That way, when you go to dispatch your event, you can construct the event as follows:
new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE, result);
That way, you're listening for the event "PropertyChangeEvent.PROPERTY_CHANGE", which never changes. The problem is now your event listener is probably listening for an event represented by the string saved in result, and obviously, this changes after the first time it's set, so there's no way to assign a listener to it.
For more information about how events work in AS3: http://livedocs.adobe.com/flex/3/html/help.html?content=events_02.html
Per the comments...
There was no event dispatcher problem.
I misdiagnosed the problem, the REAL problem was that if you have a [Bindable] property and you use a setter, and you set it for the current value, flex will ignore it. SO, you have several choices:
1) give the getter and setter different names. Seems like a "bad idea" but it does fix the problem.
2) remove [Bindable] from either the class (my problem) or the property. If the class does not implement IEventDispatcher, you will need to do so. You can simply "extends Sprite" to see it work, but that seems like a "bad idea" as a solution, so I implemented IEventDispatcher per the example at the end of this page: http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/events/IEventDispatcher.html
3) I am sure that there is a way to get around this bug, but I don't actually NEED the class to be [Bindable] so I did not find a work around.

Resources