Argument is different to argument - xamarin.forms

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.

Related

Why isn't MessagingCenter.Subscribe working?

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");

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.

PNaCl: Handle another message while already in 'HandleMessage' function?

I'm using PNaCl and I'm in a situation where first I receive a message that is handled in the 'HandleMessage' function as the normal way, but then in the current HandleMessage execution, I need to wait for a user input that would come from an other message in order to complete the execution.
I'm wondering if this is possible to do that (handling a message while already waiting in the 'HandleMessage' function) ? And if so, can someone give me a trick ?
Thanks !
HandleMessage is currently called on one thread, the main thread. So you cannot receive a message while you are handling another message.
We typically suggest you spawn a new thread to do your work, and leave the main thread to handle messages, and queue them for the new thread to handle. Take a look at the nacl_io_demo example in the SDK for an example of this technique (found in examples/demo/nacl_io).
Another solution is to use a state machine; i.e. keep track of your current state in a variable instead of on the stack.
For example:
enum State {
STATE_INIT,
STATE_WAITING_FOR_INPUT,
STATE_DO_OTHER_STUFF,
};
State state_;
virtual void HandleMessage(const pp::Var& var_message) {
switch (state_) {
case STATE_INIT:
if (var_message.AsString() == "first_message") {
state_ = STATE_WAITING_FOR_INPUT;
// Do some work before you need the user input ...
}
break;
case STATE_WAITING_FOR_INPUT:
if (var_message.AsString() == "user_input") {
// Do more work, now that we've received input from the user...
state_ = STATE_DO_OTHER_STUFF;
}
break;
}
}

Trouble with dispatching events from external AS class in Flex

I've been trying this for a day now and I can't work it out.
I have a main application Planner.mxml. This view has a couple of custom components, one of which is LoginView.mxml. In LoginView.mxml I do the following:
protected function btnLoginClick(event:MouseEvent):void
{
try
{
var login:Login = new Login(txtEmail.text, txtPassword.text);
}
catch(error:Error)
{
Alert.show(error.message, "Oops!");
}
}
I create a new instance of my Login class and send some parameters to the constructor. My constructor looks like this:
public function Login(email:String, password:String)
{
if(email == "" || password == "")
{
throw new Error("Please complete all fields.");
}
else
{
var loginRequest:HTTPService = new HTTPService();
var parameters:Object = new Object();
parameters.email = email;
parameters.password = password;
loginRequest.url = Globals.LOGIN_URL;
loginRequest.resultFormat = "object";
loginRequest.method = "POST";
loginRequest.addEventListener("result", loginHandleResult);
loginRequest.addEventListener("fault", loginHandleFault);
loginRequest.send(parameters);
}
}
Here I check if all fields are complete, and if so, I put the constructor parameters in a parameters object which I then send to the HTTPService, which is a simple PHP file that handles the request, checks with the db and returns some xml. (This might not be the best way, but this really isn't too important at this point).
If the user is logged in successfully, the xml will contain a status property which is set to true. I check for this in the result event handler of the HTTP service. This is where everything goes wrong though.
protected function loginHandleResult(event:ResultEvent):void
{
if(event.result.status == true)
{
trace("logged in");
// here stuff goes wrong
var e:LoggedInEvent = new LoggedInEvent("loggedIn");
dispatchEvent(e);
}
else
{
trace("not logged in");
Alert.show("Wrong credentials.", "Oops!");
}
}
As you can see, when the user is successfully logged in, I want to dispatch a custom event; if not, I show an alert box. However, this event doesn't dispatch (or at least, I don't know how to listen for it).
I would like to listen for it in my main application where I can then change my viewstate to the logged-in state. However, the event never seems to get there. I listen for it by having loggedIn="loggedInHandler(event)" on my loginComponent.
Any idea how to do this? Thanks in advance. I would really appreciate any help.
First, your Login needs to extend event dispatcher or implement IEventDispatcher. I'm not sure why you're getting compiler errors trying to dispatch events from it.
Next, you need to listen to the new Login instance for that event.
However, you have an architectural problem here that your View should NOT be handling business logic and it should DEFINITELY not be creating new objects that are not its own children on the Display List.
Instead, you should dispatch an event from the View that REQUESTS that a login occur, and then that request should be handled further up. Depending on the scale of your application, this can be the main mxml file or separate controller or Command logic. It is ok for the View to do a minimal amount of validation prior to dispatching the Event, but ideally you would want to encapsulate this stuff into a PresentationModel (because it is easier to test).
If you dispatch event then somebody who interested in this event must to subscribe to this event.
If you dispatch event from LoginView instance then in object who wait this event you need such lines:
loginViewInstance.addEventListemer("loggedIn", loggedInHandler);
and in handler:
private function loggedInHandler(event:LoggedInEvent):void
{
//do something
}
do what you need.

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