Dialog stack doesn't reset - botframework

I have a scorable dialog that will check if the incoming message is equal to "resetconversation". When true, the dialog resets the dialog stack in the PostAsync method.
When the postasync method is hit, the dialog stack resets as expected but when I again send a message the stack reappears and the conversation proceeds as if it was never reset.
[note]: I have noticed that this issue happens when a PromptDialog.Choice() is expecting an input.
Below is the scorable registration and the scorable dialog class.
Thanks!
Registering my scorable dialog:
Conversation.UpdateContainer(
builder =>
{
builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));
string conn = ConfigurationManager.ConnectionStrings["BotDataContextConnectionString"].ConnectionString;
var store = new SqlBotDataStore(conn);
builder.Register(c => store)
.Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
.AsSelf()
.SingleInstance();
//Scorable to check if dialog stack reset has been requested
builder.RegisterType<HandleResetRequestScorable>()
.AsSelf()
.As<IScorable<IActivity, double>>();
});
Scorable Dialog:
[Serializable]
public class HandleResetRequestScorable : ScorableBase<IActivity, bool, double>
{
private readonly IBotToUser _botToUser;
private readonly IDialogStack _dialogStack;
private bool _isMessageType { get; set; }
public HandleResetRequestScorable(IBotToUser botToUser, IDialogStack dialogTask)
{
SetField.NotNull(out _botToUser, nameof(_botToUser), botToUser);
//SetField.NotNull(out _dialogStack, nameof(_dialogStack), dialogTask);
}
protected override async Task<bool> PrepareAsync(IActivity activity, CancellationToken token)
{
string message = string.Empty;
if (activity.Type == ActivityTypes.Event)
{
_isMessageType = false;
message = activity.AsEventActivity().Name.ToLower();
}
else
{
_isMessageType = true;
message = activity.AsMessageActivity().Text.ToLower();
}
if (message == "resetconversation")
return true;
return false;
}
protected override bool HasScore(IActivity item, bool state)
{
return state;
}
protected override double GetScore(IActivity item, bool state)
{
return state ? 1.0 : 0;
}
protected override async Task PostAsync(IActivity activity, bool state, CancellationToken token)
{
try
{
var message = activity as IMessageActivity;
string[] data = message.Value.ToString().Split('|');
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity as Activity))
{
//get the conversation related data
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(default(CancellationToken));
//get the dialog stack
var convoStack = scope.Resolve<IDialogStack>();
convoStack.Reset();
if (_isMessageType)
{
await _botToUser.PostAsync("Conversation reset complete.");
await _botToUser.PostAsync("Hi");
}
await botData.FlushAsync(default(CancellationToken));
}
}
catch
{
throw;
}
}
protected override Task DoneAsync(IActivity item, bool state, CancellationToken token)
{
//throw new NotImplementedException();
return Task.CompletedTask;
}
}
}

Related

How to prevent firing of event multiple times on rapid click of a button in Xamarin forms

How can I avoid invoking of the same event multiple times when a button is clicked rapidly.
Below is the code:
I've created a Custom Delegate Command as below
View Model
namespace TestProject.ViewModels
{
public class TestViewModel
{
public CustomDelegateCommand MenuButtonClickCommand { get; set; }
public TestViewModel()
{
MenuButtonClickCommand = new CustomDelegateCommand (async () => await ShowMenuAction());
}
private async Task ShowMenuAction()
{
//await some stuff
}
}
}
CustomDelegateCommand.cs
public class CustomDelegateTimerCommand : DelegateCommand
{
public CustomDelegateTimerCommand(Action executeMethod, Func<bool> validateMethod, Action onBusy = default(Action)) : base(executeMethod)
{
BackgroundTaskWaitHandle = new EventWaitHandle(true, EventResetMode.ManualReset);
_validateMethod = validateMethod;
_onBusy = onBusy;
}
}
The problem I'm facing is whenever a user clicks on the button rapidly, the menu list popup is opening multiple times.
I have lot of commands in my project and I need a solution that would work globally.
I tried to resolve the issue like below using ObservesCanExecute() but I don't like the idea of creating a separate variable for every command as I've a lot of commands in my project and I don't want the button to go in to disabled state when CanExecute = false.
ViewModel
MenuButtonClickCommand = new CustomDelegateCommand (async () => await ShowMenuAction().ObservesCanExecute(() => CanExecute );
private async Task ShowMenuAction()
{
CanExecute = false;
//await some stuff
CanExecute = true;
}
Any help is much appreciated!
There are 2 solutions to it. One is when you use, MVVM other is when you dont.
The non MVVM solution is delaying the execution of method for certain amount of time, like this:
public class SingleClickListener
{
private bool hasClicked;
private Action<object, EventArgs> _setOnClick;
public SingleClickListener(Action<object, EventArgs> setOnClick)
{
_setOnClick = setOnClick;
}
public void OnClick(object v, EventArgs e)
{
if (!hasClicked)
{
_setOnClick(v, e);
hasClicked = true;
}
reset();
}
private void reset()
{
Android.OS.Handler mHandler = new Android.OS.Handler();
mHandler.PostDelayed(new Action(() => { hasClicked = false; }), 500);
}
}
And then when you subscribe the onclick event:
var buttonNa = new Button { Text = "Test Button" };
buttonNa.Clicked += new SingleClickListener((sender, e) =>
{
//DO something
}).OnClick;
The Mvvm solution is bit more complicated, but its not as hacky.
TestCommand = new Command(
execute: async () =>
{
IsEditing = true;
RefreshCanExecutes();
//Fire Method
TestMethod();
},
canExecute: () =>
{
return !IsEditing;
});
public void RefreshCanExecutes()
{
(TestCommand as Command).ChangeCanExecute();
}
public void TestMethod()
{
//DO something
IsEditing = false;
RefreshCanExecutes();
}
Obviously dont forget to bind your commands to xaml :)
also second solution actually disables the button, so user cannot even tap it, first one however only ignores further taps, till time delay has finished.
Create a new class which inherits from Xamarin.Forms.Button with delay in click event, than add it to your xmal.
public class DelayedButton : Xamarin.Forms.Button
{
public DelayedButton()
{
this.Clicked += DelayedButton_Clicked;
}
async private void DelayedButton_Clicked(object sender, EventArgs e)
{
this.IsEnabled = false;
await Task.Delay(Delay);
this.IsEnabled = true;
}
public int Delay { get; set; } = 500;
}
In XAML:
<yourNameSpace:DelayedButton Delay="300" Text="DelayedButton" Command="{Binding ClickCommand}"/>

Xamarin Forms MessagingCenter Subscribe called two times

I'm clicking on a product item in listview in product page viewmodel to show a popup(using rg.plugin popup) for selecting one of the product variants.After selecting variant,i am sending the selected variant to product page using messagingcenter from variant popup page viewmodel,subscribed in product page viewmodel constructor. working fine there.when i navigate to the previous page and then came back to this product page for adding one or more variant to the
same previously selected product,Messagingcenter subscribe called twice and product value increased twice.Tried to subscribe in the product page onappearing and unsubscribe in disappearing method.still calling two times? How to solve this issue?
calling popup:
var result = await dataService.Get_product_variant(store_id, product_id);
if (result.status == "success")
{
ind_vis = false;
OnPropertyChanged("ind_vis");
App.Current.Properties["product_variant_result"] = result;
App.Current.Properties["cartitems"] = purchaselist;
App.Current.Properties["selected_product"] = product_List2 ;
await PopupNavigation.Instance.PushAsync(new Popup_variant());
}
popup viewmodel: sending message
public Popup_variant_vm()
{
Radio_btn = new Command<Product_variant_list2>(Radio_stk_tapped);
product_variant_list = new List<Product_variant_list2>();
purchaselist = new ObservableCollection<Product_list2>();
show_variants();
}
internal void Confirm_variant()
{
if(App.Current.Properties.ContainsKey("selected_variant"))
{
MessagingCenter.Send<Popup_variant_vm, object>(this, "selected_variant", App.Current.Properties["selected_variant"]); //Message send from popup to product page
}
else
{
DependencyService.Get<IToast>().LongAlert("Please select any size");
}
}
product page viewmodel: subscribed here..called twice when navigating from previous page to this
public Store_page()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
var vm = new store_page_vm();
vm.Navigation = Navigation;
BindingContext = vm;
MessagingCenter.Unsubscribe<Popup_variant_vm, object>(this, "selected_variant");
MessagingCenter.Subscribe<Popup_variant_vm, object>(this, "selected_variant",async (sender, selected_variant) =>
{
var vm1 = BindingContext as store_page_vm;
vm1?.Addcart2(selected_variant);// called twice
});
}
unsubscribed in product cs page
protected override void OnDisappearing()
{
var vm = BindingContext as store_page_vm;
vm?.Save_cart();
MessagingCenter.Unsubscribe<Popup_variant_vm>(this, "selected_variant");
}
Your unsubscription should look something like below and it should work :
MessagingCenter.Unsubscribe<Popup_variant_vm, object>(this, "selected_variant");
https://stackoverflow.com/a/44753021/10937160
try this, and make sure you do not call Subscribe more than once.
My solution:
put unsubscribe sentence into subscribe body !!
MessagingCenter.Subscribe<object, string>(this, "IdSearch", (sender, arg) =>
{
listView.ItemsSource = arg;
MessagingCenter.Unsubscribe<object, string>(this, "IdSearch");
}, BindingContext);
I have created static counter variable in my app the in subscriber I have done this:
public static class Constants
{
public static int msgCenterSubscribeCounter { get; set; } = 0;
}
MessagingCenter.Subscribe<object, string>(this, "hello", (sender, arg) =>
{
Constants.msgCenterSubscribeCounter++;
if (arg.Equals("hello") && Constants.msgCenterSubscribeCounter == 1)
{
// handle your logic here
}
});
Reset counter in OnDisappearing() method from where you have called Send.
Changing Messagingcenter in to single subscription.
public class Messagingcenter_singleton
{
private static Messagingcenter_singleton _instance;
private bool isActivated = false;
private Action<string> callBackFun = null;
public static Messagingcenter_singleton Instance()
{
if (_instance == null)
{
_instance = new Messagingcenter_singleton();
}
return _instance;
}
public void setCallBack(Action<string> eventCallBack)
{
callBackFun = eventCallBack;
}
public void startSubscribe()
{
if (!isActivated)
{
isActivated = true;
MessagingCenter.Subscribe<string, string>(this, "Name", eventCallBack);
}
}
private void eventCallBack(string arg1, string arg2)
{
if (callBackFun != null)
{
InvokeMethod(new Action<string>(callBackFun), arg2);
}
}
public static object InvokeMethod(Delegate method, params object[] args)
{
return method.DynamicInvoke(args);
}
}
Use Below Code in you view model class
public void initSubscribe()
{
Messagingcenter_singleton.Instance().startSubscribe();
Messagingcenter_singleton.Instance().setCallBack(eventCallBack)
}
public void eventCallBack(string arg2)
{
// write your code here
}

TaskCanceledException on Refit call

I am implementing a simple login screen with MvvmCross Forms and Refit.
The start of the authentication task is started by pressing the button that has the command 'LogInCommand'
<Button x:Name="loginButton" Text = "Login" HorizontalOptions = "Center"
VerticalOptions = "StartAndExpand" FontSize="24" Command="{ Binding LogInCommand }"/>
This will execute the following command in the AuthenticationViewModel:
#region commands
async Task LogInTaskAsync()
{
var errors = _validator.Validate(this);
if (!errors.IsValid)
{
_toastService.DisplayErrors(errors); //Display errors here.
}
else
{
using (new Busy(this))
{
try
{
Status = AuthenticationStatusEnum.LOADING;
// get access token
string accessToken = await _authenticationService.LogIn(_email, _password);
Debug.WriteLine(String.Format("Access Token:t {0} ", accessToken));
// save token on preferences.
Settings.AccessToken = accessToken;
Status = AuthenticationStatusEnum.LOGIN_SUCESS;
}
catch (TaskCanceledException ex) {
Debug.WriteLine("Timeout Operation ...");
}
catch (Exception ex)
{
MessagingCenter.Send(new object(), EventTypeName.EXCEPTION_OCCURRED, ex);
Status = AuthenticationStatusEnum.LOGIN_FAILED;
}
}
}
}
IMvxCommand _logInCommand;
public IMvxCommand LogInCommand =>
_logInCommand ?? (_logInCommand = new MvxCommand(async () => await LogInTaskAsync()));
An instance of the AuthenticationService that is working with refit is injected into the view model.
async public Task<string> LogIn(string email, string password)
{
Debug.WriteLine(String.Format("Login with {0}/{1}", email, password));
return await _authenticationRestService.getAuthorizationToken(new JwtAuthenticationRequestDTO()
{
Email = email,
Password = password
}).ContinueWith(t => t.Result.Data.Token, TaskContinuationOptions.OnlyOnRanToCompletion);
}
The Refit service specification is as follows:
[Headers("Accept: application/json")]
public interface IAuthenticationRestService
{
[Post("/")]
Task<APIResponse<JwtAuthenticationResponseDTO>> getAuthorizationToken([Body] JwtAuthenticationRequestDTO authorizationRequest);
}
The problem is that the task is always canceled, the TaskCanceledException exception is always thrown.
I also expose CoreApp where I configure context instances.
public class CoreApp : MvvmCross.Core.ViewModels.MvxApplication
{
public override void Initialize()
{
var httpClient = new HttpClient(new HttpLoggingHandler())
{
BaseAddress = new Uri(SharedConfig.BASE_API_URL),
Timeout = TimeSpan.FromMinutes(SharedConfig.TIMEOUT_OPERATION_MINUTES)
};
// Register REST services
Mvx.RegisterSingleton<IAuthenticationRestService>(() => RestServiceFactory.getService<IAuthenticationRestService>(httpClient));
Mvx.RegisterSingleton<IParentsRestService>(() => RestServiceFactory.getService<IParentsRestService>(httpClient));
Mvx.RegisterSingleton<IChildrenRestService>(() => RestServiceFactory.getService<IChildrenRestService>(httpClient));
CreatableTypes()
.InNamespace("Bullytect.Core.Services")
.EndingWith("ServiceImpl")
.AsInterfaces()
.RegisterAsLazySingleton();
Mapper.Initialize(cfg => {
cfg.CreateMap<ParentDTO, ParentEntity>();
cfg.CreateMap<SonDTO, SonEntity>();
});
Mvx.RegisterType<IValidator, Validator>();
RegisterAppStart(new CustomAppStart());
}
}
Someone knows how I can fix this. Thanks in advance!!

Nested dialogs: message being sent twice

I have a bot where I am doing something like this:
1) A new Activity (message) arrives.
2) I dispatch the message to a RootDialog.
3) Depending on some Logic, RootDialog can:
a) Call a LuisDialog (handling natural language)
b) Call a CustomDialog (handles some business logic).
But when the user state is reset, and the flow leads to an intention inside the LuisDialog, it calls the intention method twice. Just the first time the state is empty, then it works fine.
Let me show you the code:
MessagesController:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
try
{
await Conversation.SendAsync(activity, () => new RootDialog());
}
catch (HttpRequestException e)
{
...
}
}
RootDialog:
public class RootDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await MessageReceivedAsync(context, Awaitable.FromItem(context.Activity.AsMessageActivity()));
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> awaitable)
{
bool value = DoSomeCustomLogic();
if (value)
{
string message = DoSomething();
await context.PostAsync(message);
} else {
bool value2 = DoSomeCustomLogic2();
if (value2)
{
var answerValidationDialog = new ValidateAnswerWithUserDialog();
context.Call(answerValidationDialog, ValidateAnswerWithUserDialogCompletedCallback);
} else {
var luisDialog = new LuisDialog();
await context.Forward(luisDialog,LuisDialogCompletedCallback, context.Activity, CancellationToken.None);
}
}
}
Callbacks only do context.Done(true);
And LuisDialog has an Intention which goes like this:
[LuisIntent(LuisUtils.INTENT_MENU_SALUTE)]
public async Task SaluteOrMenu(IDialogContext context, LuisResult result)
{
if (LuisUtils.IntentScoreIsHighEnough(result))
{
string userName = context.Activity.From.Name;
ContextHelper helper = new ContextHelper(MessageReceived);
await helper.AskUserToDoSomeInitialAction(context, saluteWord, userName);
context.Done(true);
}
else
{
await None(context, result);
}
}
And finally class ContextHelper:
public class ContextHelper
{
private Func<IDialogContext, IAwaitable<IMessageActivity>, Task> MessageReceived;
public ContextHelper(Func<IDialogContext, IAwaitable<IMessageActivity>, Task> messageReceived)
{
MessageReceived = messageReceived;
}
public async Task AskUserToDoSomeInitialAction(IDialogContext context, string saluteWord, string userName)
{
var reply = context.MakeMessage();
List<CardAction> buttons = BuildInitialOptionActions();
List<CardImage> images = BuildInitialOptionImages();
string initialText = $"Hi stranger!"
var card = new HeroCard
{
Title = "Hello!"
Text = initialText,
Buttons = buttons,
Images = images
};
reply.Attachments = new List<Attachment> { card.ToAttachment() };
await context.PostAsync(reply);
context.Wait(AfterUserChoseOptionInSalute);
}
private async Task AfterUserChoseOptionInSalute(IDialogContext context, IAwaitable<IMessageActivity> result)
{
await ReDispatchMessageReceivedToDialog(context);
}
private async Task ReDispatchMessageReceivedToDialog(IDialogContext context)
{
await MessageReceived(context, Awaitable.FromItem(context.Activity.AsMessageActivity()));
}
}
The SaluteOrMenu Intention gets called twice (only the first time I interact with the bot or when I delete the state. After Debugging I saw that after doing context.Wait(AfterUserChoseOptionInSalute);, the bot calls that function (instead of waiting for an event to call it)
Any ideas?
Thanks in advance.
I found the line that was wrong. It was on the first dialog (the RootDialog):
public async Task StartAsync(IDialogContext context)
{
await MessageReceivedAsync(context, Awaitable.FromItem(context.Activity.AsMessageActivity()));
}
That is a line that re dispatches a message with the incoming activity. I had it somewhere in some chunk of code and (don't know why), thought it was a good idea to use it on StartAsync. So, because of that, two calls were occurring.
Dumb me.
I just changed it to this and worked:
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}

How can I await modal form dismissal using Xamarin.Forms?

Using Xamarin.Forms how can I use make an async method that waits for the form to dismiss? If I use
await Navigation.PushModalAsync(page);
it will return once the animation is finished not when the page is dismissed.
I want a to create modal Task SignInAsync method that return true if sign-in is successful.
You can do this by triggering an event in your login page and listen for that event before going on, but you want the full TAP support and I second you there. Here's a simple yet working 2 page app that does just this. You'll obviously want to use ContentPage custom subclass and have proper methods instead of my quick Commands, but you get the idea, and it saves me typing.
public static Page GetFormsApp ()
{
NavigationPage navpage = null;
return navpage = new NavigationPage (new ContentPage {
Content = new Button {
Text = "Show Login dialog",
Command = new Command (async o => {
Debug.WriteLine ("Showing sign in dialog");
var result = await SignInAsync (navpage);
Debug.WriteLine (result);
})
}
});
}
static Task<bool> SignInAsync (NavigationPage navpage)
{
Random rnd = new Random ();
var tcs = new TaskCompletionSource<bool> ();
navpage.Navigation.PushModalAsync (new ContentPage {
Content = new Button {
Text = "Try login",
Command = new Command ( o => {
var result = rnd.Next (2) == 1;
navpage.Navigation.PopModalAsync ();
tcs.SetResult (result);
})
}
});
return tcs.Task;
}
The minor drawback is that the Task<bool> returns before the end of the pop modal animation, but that's:
easy to fix
only an issue if you're awaiting that result to push a new modal Page. Otherwise, meh, just go on.
Override OnAppearing
Firstly, it's worth noting that simply overriding OnAppearing in the calling Page may suffice in many circumstances.
protected override void OnAppearing()
{
base.OnAppearing();
...
// Handle any change here from returning from a Pushed Page
}
(note that the pushed page's OnDisappearing override is called after the caller's OnAppearing - seems a bit backwards to me!)
AwaitableContentPage
Secondly...this is my take on #Chad Bonthuys answer:
public class AwaitableContentPage : ContentPage
{
// Use this to wait on the page to be finished with/closed/dismissed
public Task PageClosedTask { get { return tcs.Task; } }
private TaskCompletionSource<bool> tcs { get; set; }
public AwaitableContentPage()
{
tcs = new System.Threading.Tasks.TaskCompletionSource<bool>();
}
// Either override OnDisappearing
protected override void OnDisappearing()
{
base.OnDisappearing();
tcs.SetResult(true);
}
// Or provide your own PopAsync function so that when you decide to leave the page explicitly the TaskCompletion is triggered
public async Task PopAwaitableAsync()
{
await Navigation.PopAsync();
tcs.SetResult(true);
}
}
And then call it thus:
SettingsPage sp = new SettingsPage();
await Navigation.PushAsync(sp);
await sp.PageClosedTask; // Wait here until the SettingsPage is dismissed
Just thought I would contribute to this one, although it's been a while since it was asked and answered. I built upon the answer by #noelicus. I wanted a generic way to do this with multiple situations so the Task needs to be able to return not just bool but anything. Then, using generics:
public class AwaitableContentPage<T> : ContentPage
{
// Use this to wait on the page to be finished with/closed/dismissed
public Task<T> PageClosedTask => tcs.Task;
// Children classes should simply set this to the value being returned and pop async()
protected T PageResult { get; set; }
private TaskCompletionSource<T> tcs { get; set; }
public AwaitableContentPage()
{
tcs = new TaskCompletionSource<T>();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
tcs.SetResult(PageResult);
}
}
Now, in the page you want to run as modal, you can do:
public partial class NewPerson : AwaitableContentPage<Person>
and when done, simply do:
base.PageResult = newPerson; // object you created previously
await base.Navigation.PopAsync();
Then, to make it super simple to use, use an extension method:
public static class ExtensionMethods
{
async public static Task<T> GetResultFromModalPage<T>(this INavigation nav, AwaitableContentPage<T> page)
{
await nav.PushAsync(page);
return await page.PageClosedTask;
}
That's all. Now, in your code, in any page where you want to use this, the syntax ends up simply like this:
Person newPerson = await Navigation.GetResultFromModalPage<string>(new NewPersonCreatePage());
if (newPerson != null)
UseNewPersonCreatedByOtherPage();
Hope this helps!
In my implementation I used:
await navigation.PopModalAsync();
Full Example:
private INavigation navigation;
public LoginPageModel(INavigation navigation, LoginPage loginPage)
{
this.navigation = navigation;
this.loginPage = loginPage;
}
public bool IsValid { get; set; }
protected async void ExecuteLoginCommand()
{
var loginResult = await AuthenticationHelper.Authenticate(Email, Password);
var isValid = false;
if (loginResult != null)
{
isValid = true;
}
//return isValid;
AuthenticationResult(isValid);
}
private async void AuthenticationResult(bool isValid)
{
if (isValid)
{
Debug.WriteLine("Logged in");
await navigation.PopModalAsync();
}
else
{
Debug.WriteLine("Failed" + email + password);
await loginPage.DisplayAlert("Authentication Failed", "Incorrect email and password combination","Ok", null);
}
}
The answer selected and given by #Stephane Delcroix above is awesome. But for anybody willing to push this further, by waiting for a page's completion and returning more structured data in a good MVVM fashion, you could do the following:
By calling an event from the page's OnDisapearing method, this event can then be subscribed by the navigation service which you create, and you can then use the "TaskCompletionSource" to wati until your page finishes its work and then complete the task.
For more details about accomplishing this, you can check this blog post.
Here is the base page's implementation, every page in this demo app inherit this page:
public class BasePage<T> : ContentPage
{
public event Action<T> PageDisapearing;
protected T _navigationResut;
public BasePage()
{
}
protected override void OnDisappearing()
{
PageDisapearing?.Invoke(_navigationResut);
if (PageDisapearing != null)
{
foreach (var #delegate in PageDisapearing.GetInvocationList())
{
PageDisapearing -= #delegate as Action<T>;
}
}
base.OnDisappearing();
}
}
Here is an overview of the navigation service you should use:
public async Task<T> NavigateToModal<T>(string modalName)
{
var source = new TaskCompletionSource<T>();
if (modalName == nameof(NewItemPage))
{
var page = new NewItemPage();
page.PageDisapearing += (result) =>
{
var res = (T)Convert.ChangeType(result, typeof(T));
source.SetResult(res);
};
await App.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page));
}
return await source.Task;
}
To call this page with the navigation service, you can use the following code:
var item = await new SimpleNavigationService().NavigateToModal<Item>(nameof(NewItemPage));
Items.Add(item);

Resources