How to use Messaging center in xamarin forms - xamarin

I am trying to use messaging center instead of Messenger in xamarin forms I have no idea about messaging center I tried Bellow code to subscribe and Send Message in xamarin forms
MessagingCenter.Send(this, "TodoTable", "Todo");
But I have not Idea from where I can subscribe to this message I tried bellow code :
MessagingCenter.Subscribe<TodoClass>(this, Todo, async (sender, arg) =>
{
await RefreshCommand.ExecuteAsync();
});
This is giving me error Any Help will appreciated :)

It is a quirk of XF messaging centre that (it seems) you need to know who will be sending the message, and potentially who will be receiving it.
However it can be object. The signature of subscribe is:
void MessagingCenter.Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender sender = null)
The trick is to subscribe thus:
MessagingCenter.Subscribe<object>(this, "messageName", Action<object> callback)
This says object or anything derived from object can be a sender, ie, everything.
However if you want to only subscribe to messages sent by a particular instance of a type:
MessagingCenter.Subscribe<MyClass>(this, "messageName", Action<MyClass> callback)
The use of the full signature is a bit suspect. Basically it is saying only if sent from the source object are subscribers who used that source object when subscribing.
MessagingCenter.Subscribe<object, string>(this, "Hi",
(sender, arg) =>
{
DisplayAlert("Message Received", "arg=" + arg, "OK");
},
BindingContext);
if you use the following to send the message it wont be received by the subscriber just above:
MessagingCenter.Send<object, string>(this, "Hi", "John");
But the following will be received
MessagingCenter.Send<object, string>(BindingContext, "Hi", "John");
Though why would you want to send a message to yourself. (Assuming the subscribe and send were in the same page in this case).
However if there were multiple pages with the exact same binding context the message will be sent to all such subscribers. Eg, pages bound to the same view model.

To improve the answer by #user2825546, if you wish to subscribe to only messages from your view-models, you need to specify the base class type when sending the message:
MessagingCenter.Subscribe<BaseViewModel, string>(this, "ShowError", (view, message) => { });
public class StartupViewModel : BaseViewModel
{
//Like this...
MessagingCenter.Send<BaseViewModel, string>(this, "ShowError", "Message");
//Or...
MessagingCenter.Send((BaseViewModel)this, "ShowError", "Message");
}
When testing, I tried to send the message as StartupViewModel, but the listener was not receiving the messages. I guessed that it would, since the class derives from the BaseViewModel.

Send Method
MessagingCenter.Send<Application>(Application.Current,"RefreshDocs");
Subscribe Method
MessagingCenter.Subscribe<Application>(Application.Current , "RefreshDocs" , (sender) =>
{
});

The goal of MVVM is to abstract away your Views from your Business Logic. This ensures great code reuse, testability, and is pretty awesome. Many MVVM Frameworks offer tools to enhance this such as data binding and dependency services to make our lives easier. These are built into Xamarin.Forms, which is awesome, but one feature less talked about is the Messaging Center. It’s entire goal is to enable ViewModels or other components to communicate with each other without having to know anything about each other besides a simple Message contract.
So for instance, let’s say you are in a master/detail setup where your MasterViewModel has a list of items and your DetailViewModel allows you to create a new item, update an item, or delete an item. When your user is on the detail page and triggers an event you need to somehow message back to your MasterViewModel that has a list of Items so the UI can react on the Master page when we navigate back.
So let’s say our MasterViewModel subscribes to “Update” and “AddNew” message events. It will then update it’s observable collection based on when it receives messages. Our DetailViewModel would then send a message in our SaveCommand to notify anyone that is subscribed to these specific messages:
public ObservableCollection<TripExpense> Expenses { get; set; }
public ExpensesViewModel()
{
Expenses = new ObservableCollection<TripExpense>();
//Subscibe to insert expenses
MessagingCenter.Subscribe<TripExpense>(this, "AddNew", (expense) =>
{
Expenses.Add(expense);
});
//subscribe to update expenxes
MessagingCenter.Subscribe<TripExpense>(this, "Update", (expense) =>
{
ExecuteUpdateExpense(expense);
});
}
private async Task ExecuteSaveCommand()
{
if (IsBusy)
return;
IsBusy = true;
//Send a message to insert/update the expense to all subscribers
if(isNew)
{
MessagingCenter.Send(expense, "AddNew");
}
else
{
MessagingCenter.Send(expense, "Update");
}
IsBusy = false;
navigation.PopAsync();
}
There you have it, messaging made easy! Don’t forget to unsubscribe if you no longer wish to receive notifications.

Related

How can I have one page subscribe to messages from multiple different pages using Xamarin MessagingCenter?

Here's the situation that I have. On DeckPage, I subscribe to a message like this:
public void OnAppearing()
{
RefreshLables();
MessagingCenter.Subscribe<CardFormatPageViewModel>(
this,
"RefreshDeckConfigsLabels",
sender => { RefreshLables(); });
}
private void RefreshLables()
{
-- refresh code
}
public void OnDisappearing()
{
MessagingCenter.Unsubscribe<CardFormatPageViewModel>(this,"RefreshDeckConfigsLabels");
}
This works good and I am sending a message from the CardFormatPageViewModel like this:
MessagingCenter.Send(this, "RefreshDeckConfigsLabels");
I also want to be able to send messages to DeckPage from OptionPageViewModel:
MessagingCenter.Send(this, "RefreshDeckConfigsLabels");
Plus from more models.
Is there a way I can code the subscribe so I can subscribe to one common message that will be accepted if it comes from CardFormatPageViewModel, OptionPageViewModel or the other View Models that need to make it refresh or do I have to do a subscription for every one of them?
sure
MessagingCenter.Send<object>(this, "RefreshDeckConfigsLabels");
and
MessagingCenter.Subscribe<object>(
this,
"RefreshDeckConfigsLabels",
sender => { RefreshLables(); });

Wait for Even type Activity in a waterfallstep dialog (bot framework 4.0)

It's possible to wait and receive an Event type activity in a waterfall step dialog. I use directline 3.0 and inside a dialog flow I send an event from the bot to the client. After i would like to send an event from the client to the bot as an answer to previous send. If i use prompt await dc.Prompt("waitEvent",activity) where waitEvent is a textprompt and i answer with a message it works fine but I would like to answer to an event with an event. I was thinking that i could write a custom prompt but i didn't find documentation and obviously I could manage the conversation flow but I prefer use Dialogs where possible
You can use the ActivityPrompt abstract class to build an "EventActivityPrompt" class.
There aren't any BotFramework samples of this usage yet, but there are new tests written by the BotFramework team that you can use as an example.
To create your own EventActivityPrompt, you just need to implement the ActivityPrompt like so:
public class EventActivityPrompt : ActivityPrompt
{
public EventActivityPrompt(string dialogId, PromptValidator<Activity> validator)
: base(dialogId, validator)
{
}
}
The core difference between an ActivityPrompt and other Prompts (besides its abstract status) is that ActivityPrompts require a PromptValidator<Activity>, in order to validate the user input.
The next step is to create your validator. Here is the example:
async Task<bool> _validator(PromptValidatorContext<Activity> promptContext, CancellationToken cancellationToken)
{
var activity = promptContext.Recognized.Value;
if (activity.Type == ActivityTypes.Event)
{
if ((int)activity.Value == 2)
{
promptContext.Recognized.Value = MessageFactory.Text(activity.Value.ToString());
return true;
}
}
else
{
await promptContext.Context.SendActivityAsync("Please send an 'event'-type Activity with a value of 2.");
}
return false;
}

What's the best way of handling ViewModel destroying for CancellationToken activation with MvvmCross?

I am having an MvvmCross ViewModel, which calls different async methods of my DataService.
Similar to the following:
public class LoginViewModel : MvxViewModel
{
private readonly IIdentityService _dataService;
private CancellationTokenSource _viewModelCancellationTokenSource;
public IMvxCommand GoLogin { get; set; }
public LoginViewModel(IIdentityService identityService)
{
_dataService = identityService;
_viewModelCancellationTokenSource = new CancellationTokenSource();
GoLogin = new MvxCommand(async () => await ProcessLogin());
}
private async Task ProcessLogin()
{
// calling the dataservice which must stop processing
// (to cancel) in case if the ViewModel is being destroyed
await _dataService.AssureIsLoggedIn(data, _viewModelCancellationTokenSource.Token);
await NavigationService.Navigate<LoginNextStepViewModel>();
}
public override void ViewDestroy(bool viewFinishing = true)
{
base.ViewDestroy(viewFinishing);
// not sure if that is a right (and working) place
_viewModelCancellationTokenSource.Cancel();
}
}
So, MvvmCross is quite unclear about the part with the ViewModel destroying. It describes Construction, Init, Reload and Start, but doesn't say any definite regarding the destroying:
Monitoring other View/ViewModel lifecycle event across multiple
platforms is fairly tricky, especially once developers start
experimenting beyond the ‘basic’ presentation models and start using
tabs, splitviews, popups, flyouts, etc
For most viewmodels, it’s common to not try to monitor other lifecyle
events. This is OK since most viewmodels don’t perform any actions and
don’t consume any resources when the view is not present - so these
can just be left to be garbage collected when the system needs the
memory back.
However, besides the custom platform situations, there are still many cases like navigating back from the view model, or (again) navigation away from current viewmodel with its following closing.
So, what's the best way to handle it then?
From your code:
// calling the dataservice which must stop processing
// (to cancel) in case if the ViewModel is being destroyed
The ViewModel won't be destroyed before the async methods finish executing. I think you are confusing the View with the ViewModel.
In case of a login page, you would usually prevent the user from navigating away from it until your server call goes through.
If for some reason you want to cancel then you need to decide what scenarios you want to handle, there is no single universal place. Your options are the view callbacks:
void ViewDisappearing();
void ViewDisappeared();
void ViewDestroy();
and the navigation events:
event BeforeNavigateEventHandler BeforeNavigate;
event AfterNavigateEventHandler AfterNavigate;
event BeforeCloseEventHandler BeforeClose;
event AfterCloseEventHandler AfterClose;
event BeforeChangePresentationEventHandler BeforeChangePresentation;
event AfterChangePresentationEventHandler AfterChangePresentation;

Xamarin form MessagingCenter Unsubscribe is not working as expected

Functionality written inside the MessagingCenter.Subscribe() is called multiple times when i navigate to and fro multiple times in the application. But each time before subscribing, i do unsubscribe to the same in constructor as follows, still it didn't worked.
MessagingCenter.Unsubscribe<SubmitPage>(this,"Save");
MessagingCenter.Subscribe<SubmitPage>(this, "Save", (sender) =>
{
DisplayToastOnSuccessfulSubmission();
});
In my application i have 6 pages(git) and i save the data in 6th page with MessagingCenter.Send and same will be subscribed in 2nd page and saved message will be displayed in 2nd page(after navigating to that page).
Now i navigate like 2->1->2->3->4->5->6 in this particular case DisplayToastOnSuccessfulSubmission() would be called two times(because Page2 constructor is called twice).
I even tried placing the same code in OnAppearing.
I can't unsubscribe in OnDisappear as I need the event wiring up to when I reach Page6 for save.
Reproduced the same behaviour in sample project and added here https://github.com/suchithm/MessageCenterSampleApp Drop box link
What is the proper way to do this?
But each time before subscribing, I do unsubscribe to the same in constructor as follows, still it didn't worked.
MessagingCenter.Subscribe() is called multiple times, because there are two instances of Page2 in your code, both of them use MessagingCenter.Subscribe() method, that's why the Unsubscribe didn't work.
You can modify page2() to a singleton to make sure there is only one instance of Page2 in your project, after that when you send a message,
the MessagingCenter.Subscribe() is called only once.
Page2.cs:
public static Page2 instance = new Page2();
public static Page2 GetPage2Instance()
{
if(instance == null)
{
return new Page2();
}
return instance;
}
private Page2()
{
InitializeComponent();
MessagingCenter.Unsubscribe<Page2>(this, "SaveToastPage2");
MessagingCenter.Subscribe<Page2>(this, "SaveToastPage2", (sender) =>
{
DisplayToastOnSuccessfulSubmission();
}
}
When you send a message :
MessagingCenter.Send(Page2.GetPage2Instance(), "SaveToastPage2");
EDIT :
Remember that declaring constructors of Page2 class to be private to make sure there is only one instance of Page2 in your project sure.
private Page2()
{
...
}
Modify your Page1.cs code :
async void Handle_Next(object sender, System.EventArgs e)
{
await App.NavigationRef.PushAsync(Page2.GetPage2Instance(), true);
}
I faced same issue. I solved issue by passing the same parameters inn subscribe and unsubscribing as well.
MessagingCenter.Subscribe<Page1, T>(this, "Listen", async (Page1 arg1, T
listenedString) =>
{
});
Unsubscribe like below
MessagingCenter.Unsubscribe<Page1, T>(this, "Listen");
I'm using this temporary solution.
I declared a static dictionary to storage my object (to this example I used an object type).
private static Dictionary<string, object> subscribedReferencePages = new Dictionary<string, object>();
And I always storage the last subscribed page reference.
Then I compare the page reference before triggering the message method to fire only the last one.
subscribedReferencePages[pageName] = this;
MessagingCenter.Subscribe<ViewModelBase>(this, pageName, async (sender) =>
{
if (!ReferenceEquals(sender, this))
{
return;
}
this.OnInitialized();
});
To call the message method I need to pass the dictionary as parameter (instead of the "this" reference).
MessagingCenter.Send(subscribedPages[pageName], keyPageName);
Instead of unsubscribing when you navigate TO a page,
unsubscribe when you navigate AWAY from the page. At that point your instance of 'this' is still the same 'this' you think it is.

Ms BotBuilder : firstRun dialog prevents triggering of other dialogs based on LUIS intents

I have a firstRun dialog defined in the bot like this :
// First run dialog
bot.dialog('firstRun', [
function (session, next) {
session.userData.token = _.get(session, 'message.user.token', null) || _.get(session, 'userData.token', null)
}
]).triggerAction({
onFindAction: function (context, callback) {
var score = 0;
if (session.userData.token doesn't exist or new token recieved in `session.user.message.token`){
score = 1.1;
}
callback(null, score);
}
});
And there's a LUIS model integrated with a dialog that triggers on an intent, let's say Help :
bot.dialog('help', [
(session, args) => {
let entities = _.get(args, 'intent.entities', null);
let topic = _.get(builder.EntityRecognizer.findEntity(entities, 'topic'), 'entity', null) || _.get(args, 'option', null);
session.replaceDialog(topic);
}
])
.triggerAction({
matches: 'Help'
});
The onFindAction triggers on every message. And it triggers firstRun only on the first message when session.userData.token is not set.
Problem is, if the first message is matched to Help intent, it does not get triggered. It works from the second time, when firstRun is not triggered.
How can I ensure any matching intent triggers the corresponding dialog, irrespective of firstRun?
If there's a different way possible to achieve the same thing, please suggest.
Addition
What I am trying to accomplish is this - I have a web service auth token that I want to keep in session.userData.token that refreshes every hour. So right now I trigger onFindAction on every utterance which checks if either session.userData.token doesn't exist (which means its the first utterance) OR a new token has been sent. In both cases I trigger firstRun to update session.userData.token and proceed to trigger any dialog that matched with the LUIS intent of the utterance. But whenever firstRun is triggered, none of the other dialogs are triggered. It would be ideal to have a simpler mechanism to do this i suppose.
Thanks
It sounds like you're trying to have a pass-through intent handler that would trigger before the message is routed to the actual handlers. Middleware would be the best place to handle your token refresh logic, but working with session in your middleware isn't easy. This blog post of mine explains why - http://www.pveller.com/smarter-conversations-part-4-transcript/.
Your best bet is the routing event, I believe. It's synchronous via events and you are given the session object. You should be able to validate and refresh your token as needed before the message reaches the proper intent handler destination.
bot.on('routing', function (session) {
if (!session.userData.token) {
// receive new token
}
});
Unlike middleware though, you are not given the next callback to continue the chain, so you will have to make sure you fetch the token synchronously. The blog post I mentioned previously explains this part as well.

Resources