Why isn't MessagingCenter.Subscribe working? - xamarin.forms

In Xamarin forms, I Subscribe in OnAppearing and Unsubscribe in OnDisappearing. But it still calling the callback for every instance.
protected override void OnAppearing(){
if (isGoingBack)
MessagingCenter.Subscribe<PhoneNumberVerificationPajModal, string>(this, "Phone.Verify", codeSendRequest);
}
protected override void OnDisappearing(){
if (isGoingBack)
MessagingCenter.Unsubscribe<PhoneNumberVerificationPajModal>(this, "Phone.Verify");
}
Truth is calling Unsubscribe works. But, in my code I have a condition when the click on a button I open a Page. As that page is the one sending the message, when the user click the button I set isGoingBack = false. When I Press the back button and coming back to the page, I can Unsubscribe and Subscribe get called. But going to the next the callback event get call the same number of time I created a new page. But the truth is I Unsubscribe whenever I leave the page.

in your sample you are not passing the arg type to the unsubscribe method
try unsubscribing with the same type parameter you are subscribing with.
so instead of
MessagingCenter.Unsubscribe<PhoneNumberVerificationPajModal>(this, "Phone.Verify");
try
MessagingCenter.Unsubscribe<PhoneNumberVerificationPajModal, string>(this, "Phone.Verify");

Related

Argument is different to argument

This is in a constructor...
MessagingCenter.Unsubscribe<object, AppointmentDetail>(this, "AppointmentLoaded");
MessagingCenter.Subscribe<object, AppointmentDetail>(this, "AppointmentLoaded", async (sender, a) =>
{
if (appointment.AppointmentId == a.AppointmentId && appointment != a) // a member property
{
...
}
});
This is called in the same page...
MessagingCenter.Send<object, AppointmentDetail>(this, "AppointmentLoaded", appointment);
The handler function is called and the appointment sent with the notification is different to the appointment that arrives at the handler.
How is that possible?
Additionally, the handler runs twice even though the message is only sent once.
Any ideas?
If I put a breakpoint on the message send, then it runs correctly. So I'm guessing there's some debugging artifact at play.
This was happening because I'd created two pages, one either side of a login process, and the first page was still receiving messages.

ViewModel event fires multiple times

I'm using MVVM Light for my application and I have also implemented the INavigationService for going back/for between pages.
So in a common scenario, it's like this
MainPage > Categories > Rounds > DataPage.
In the DataPage, I'm making a request to fetch the results and depending on the result returned from the callback I call the .GoBack() method to pop the current page from the stack and return to Rounds.
What I have noticed is that if I hit first the DataPage and the .GoBack() gets called and then tap on a different round the callback method will be fired twice, and if I go back and in again thrice, and continues like this.
Essentially this means that the .GoBack() will be called again and the navigation gets messed up.
I believe this has to do with not cleaning up the previous VM's, I tried changing this behavior with the UnRegister / Register class from SimpleIOC but no luck.
In the ViewModel class
public void UnsubscribeFromCallBack()
{
this.event -= method;
}
In the .xaml.cs page
protected override void OnDisappearing()
{
base.OnDisappearing();
PageViewModel vm = (this.BindingContext as PageViewModel);
vm.UnSubscribeFromCallback();
}

Updating UI BeginInvokeOnMainThread not reflecting immediately

I am showing activity indicator after clicking login button until redirecting the user to another page, to make them understand some progress is going on. But after clicking login button Activity Indicator is not shown immediately, it is shown after few seconds,
Why its so? To reduce that delay only I am putting activity indicator...
My Code:
async void loginButtonGesture_Tapped(object sender, EventArgs e)
{
Device.BeginInvokeOnMainThread(() =>
{
loadingPanel.IsRunning = true;
loadingPanel.IsVisible = true;
});
}
Does the method have to be async void? It seems like this particular scheduling anything on the main thread shouldn't need to be async. Try that to see if it changes anything. Also you could try to set breakpoints on the Device.BeginInvokeOnMainThread line, and the loadingPanel.IsRunning... line to see where the delay happens.
First of all, loginButtonGesture_Tapped() event handler is triggered by UI thread so you don't need to use Device.BeginInvokeOnMainThread(), it is already in UI thread. But since you used Device.BeginInvokeOnMainThread() here, the reason for the delay is because on Android, your code inside of BeginInvokeOnMainThread() is added to MainLooper's message queue,(your code is not executed immediately) and is executed when the UI thread is scheduled to handle its messages.
The detailed answer can be found in Xamarin document:
For iOS:
IOSPlatformServices.BeginInvokeOnMainThread() Method simply calls NSRunLoop.Main.BeginInvokeOnMainThread
public void BeginInvokeOnMainThread(Action action)
{
NSRunLoop.Main.BeginInvokeOnMainThread(action.Invoke);
}
https://developer.xamarin.com/api/member/Foundation.NSObject.BeginInvokeOnMainThread/p/ObjCRuntime.Selector/Foundation.NSObject/
You use this method from a thread to invoke the code in the specified object that is exposed with the specified selector in the UI thread. This is required for most operations that affect UIKit or AppKit as neither one of those APIs is thread safe.
The code is executed when the main thread goes back to its main loop for processing events.
For Android:
Many People think on Xamarin.Android BeginInvokeOnMainThread() method use Activity.runOnUiThread(), BUT this is NOT the case, and there is a difference between using runOnUiThread() and Handler.Post():
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);//<-- post message delays action until UI thread is scheduled to handle messages
} else {
action.run();//<--action is executed immediately if current running thread is UI thread.
}
}
The actual implementation of Xamarin.Android BeginInvokeOnMainThread() method can be found in AndroidPlatformServices.cs class
public void BeginInvokeOnMainThread(Action action)
{
if (s_handler == null || s_handler.Looper != Looper.MainLooper)
{
s_handler = new Handler(Looper.MainLooper);
}
s_handler.Post(action);
}
https://developer.android.com/reference/android/os/Handler.html#post(java.lang.Runnable)
As you can see, you action code is not executed immediately by Handler.Post(action). It is added to the Looper's message queue, and is handled when the UI thread's scheduled to handle its message.

Linq data not updating form after postback

In my application, I have a form that users fill out, then gets approved by a manager. I have various types of forms that all use the same process, so the approval buttons are all done via a user control (which includes the functionality to update the data in the database and call the postback).
However, once I click on the "Approve" button (which is in the user control), the form information doesn't update (it still says "unapproved"). A postback is definitely happening, but not sure why the page isn't updating properly.
I can confirm that the change are being made - when I manually reload the page, it gets updated - but not on the post back.
What am I missing here?
My page:
protected void Page_Load(object sender, EventArgs e)
{
int ID;
// ensure that there's an ID set in the query string
if (Int32.TryParse(Request.QueryString["ID"], out ID))
PopulatePage(ID);
else
Response.Redirect("~/Default.aspx");
}
}
protected void PopulatePage(int ID)
{
using (WOLinqClassesDataContext db = new WOLinqClassesDataContext())
{
lblStatus.Text = wo.Workorder.status;
....
}
}
I think that the Page_Load happens before the code in the submit button. To check this just use a couple of breakpoints. So the page loads the old data since the new data are not saved yet.
You should call a method to load the data inside the OnClick method of the Approve button.
After you've submitted the changes to the database, try running db.Refresh(RefreshMode.OverwriteCurrentValues) to force the changes to be reloaded into the data context.

Flex Script and Event Question

I wonder how to achieve this in Flex.
Basically, I have enabled Drag and Drop in some of my list controls.
<mx:DataGrid id="dg1" width="100%" height="100%" dataProvider="{xmllcData}"
dropEnabled="true" dragDrop="dg1_dragDropHandler(event)">
</mx:DataGrid>
In the function dg1_dragDropHandler event, I have the following codes:
private function dg1_dragDropHandler(evt:DragEvent):void
{
// Perform some actions here...
// .......
// Show Message to Confirm.
Alert.show('Proceed?', 'Title', Alert.YES | Alert.NO, null, handleAlert, null, Alert.YES);
}
private function handleAlert(evt:CloseEvent):void
{
if (evt.detail == Alert.YES)
{
// Perform the functions as necessary
}
else
{
// Execute the script to prevent the dropping of the object.
// How can I call the DragEvent.preventDefault(); function from here?
}
}
In the codes above, I want to call the preventDefault() on the alertHandler function since the other scripts after the call to the Alert.show in dg1_dragDropHandler event would be executed concurrently with the alert.show.
How would I be able to reference the DragEvent of the dg1_dragDropHandler event from the alertHandler event?
Instead of specifiying your listener function, handleAlert(), as a normal function, you can use an anonymous function. Write your code like this:
private function dg1_dragDropHandler(evt:DragEvent):void
{
// Perform some actions here...
// .......
// Show Message to Confirm.
Alert.show('Proceed?', 'Title',
Alert.YES | Alert.NO,
null,
function(evt:CloseEvent) {
if (evt.detail == Alert.YES) {
// Perform the functions as necessary
}
else {
// Execute the script to prevent the dropping of the object.
// Now you have access to the evt:DragEvent!
}
},
null, Alert.YES);
}
}
When you use an anonymous function, you still have access to all the variables in your current scope. This means you can still access the evt:DragEvent variable. As Glenn said though, I don't know if this will solve your default action problem.
You probably want to store the details of the dropEvent in a local variable. Then when you want to do your "preventDefault" part, just access the event object and do your magic.
Not sure why you want to preventDefault though. I'm not quite understanding that part. Wouldn't all the other listeners of the event run to completion while the program is waiting for you to say YES/NO to the alert?
Which other parts of the callstack are operating here? You could stop anything else in the event chain from happening by calling event.stopImmediatePropergation(); on the first line of your dragDropHandler (assuming that the listener has a higher priority than others in the chain).
You would then need to manually replicate the drag and drop operations on confirm, which I'm not sure but you could achieve using the doDrag() method of the DragManager.
DragManager.doDrag() langauge reference
You're absolutely right that the Alert will be popped up asynchronously with respect to the original DragEvent dispatch.
Since you don't want the default datagrid behavior to kick in at that point, you need to call preventDefault() on receipt of the event, and then throw up your alert panel.
Then, in the success branch of your alert handler, you could try to rethrow ( throw a new) DragEvent. Use a local variable to keep track of the original event details so that you can clone() or simply create a new event with the same properties. Basically, you're intercepting and interrupting the event flow and then attempting to resume it later.
Haven't tried this myself, but that's what I'd explore first.
I have not tried this myself, but preventing default behavoiur immediately is the only way to stop the grid from performing the copy or move.
Try preventing the default behaviour and maintaining the drag event. Then, if you user hits no, you have already stopped the event. If the user hits yes, you can (this is the part i am unsure of) re-dispatch the drop event on the grid. Hopefully it will behave normally. To get the event into your Alert handler you can simply use the data property on the Event window to track it.
private function dg1_dragDropHandler(evt:DragEvent):void
{
// Perform some actions here...
// .......
evt.preventDefault();
// Show Message to Confirm.
Alert.show('Proceed?', 'Title', Alert.YES | Alert.NO, null, handleAlert, null, Alert.YES).data = evt;
}
private function handleAlert(evt:CloseEvent):void
{
if (evt.detail == Alert.YES)
{
// Perform the functions as necessary
var dgEvt:DragEvent = Alert(evt.currentTartet).data;
var newDrag:DragEvent; //need a new event because the default behaviour on the old one is still prevented
//copy event values to new drag event
dg1.dispatchEvent(newDrag);
}
else
{
// Execute the script to prevent the dropping of the object.
// How can I call the DragEvent.preventDefault(); function from here?
}
Again, not entirely sure if it will work, just off the top of my head. Of course, you have to remove the custom dragDrop event handler from your grid before you redispatch the approved drag, otherwise your handler with prevent the default, then pop an alert and repeat over and over.

Resources