Problem with messagingCenter

CaioshinCaioshin ITMember ✭✭
edited August 2015 in Xamarin.Forms

Hello,
I've a problem using messages to drive the navigation from an async call.
I try to explain better. My problem is really simple: I've a login page. In this page I subscribe to navigate when a certain message is received:

MessagingCenter.Subscribe<ViewModelBase,string>(this, HelperClass.LOGIN_SUCCESSFULL, (sender, username) =>
            {
                ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(ViewModelLocator.CouponsPageKey, username);
            });

Then, in its viewmodel, when a user press the login button, a relaycommand is triggered to make an async call to a WCF, in this way:

 public RelayCommand LoginCommand
        {
            get
            {
                return _loginCommand
                       ?? (_loginCommand = new RelayCommand(
                           () =>
                           {
                               Loading = true;
                               HelperClass.ClientWS.LoginAsync(Username, Password);
                           }));
            }
        }

When this service has replied the handler is triggered

    void ClientWS_LoginCompleted(object sender, ValidazioneUtenteCompletedEventArgs e)
    {
        var _res = e.Result;

                if (_res)
                {
                    MessagingCenter.Send<ViewModelBase, string>(this, HelperClass.LOGIN_SUCCESSFULL, Username);
        }
    }

I can see that the message is correctly received because with the debugger the navigateTo is executed.

What's the strange?
Is that in this way I can't see my app navigate to the new page, while if inside of my LoginCommand instead to call the

HelperClass.ClientWS.LoginAsync(Username, Password);

I directly send the message the navigation occurs correctly.
What's wrong?

Thank you

Posts

  • ylemsoulylemsoul RUMember ✭✭✭

    ClientWS_LoginCompleted is executed on the background thread. You need to post continuation to the UI thread via Device.BeginInvokeOnMainThread(..), for example:
    Device.BeginInvokeOnMainThread(() => MessagingCenter.Send(..));

    For async better to make Task-based operations (TAP) on top of APM or EAP (like in your case) so then you can use async/await and don't need to handle threading by yourself especially in VM.

  • CaioshinCaioshin ITMember ✭✭
    edited August 2015

    you're great, it works.
    I was convinced that using MessagingCenter I shouldn't have to worry about the thread of UI, because the message is received by main thread (in Login.xaml.cs), I was wrong.

    About EAP what would be a simple best implementation suggested?

    Thank you a lot

  • ylemsoulylemsoul RUMember ✭✭✭
    edited August 2015
  • CaioshinCaioshin ITMember ✭✭

    ok thank you, so I will have to return Task from my relaycommand making it async (I can't await it)?
    Another doubt is that I can declare the handler in the body of my eventhandler (like in this example:

    public static Task<string> DownloadStringAsync(Uri url)
    {
        var tcs = new TaskCompletionSource<string>();
        var wc = new WebClient();
        wc.DownloadStringCompleted += (s,e) =>
        {
            if (e.Error != null) tcs.TrySetException(e.Error);
            else if (e.Cancelled) tcs.TrySetCanceled();
            else tcs.TrySetResult(e.Result);
        };
        wc.DownloadStringAsync(url);
        return tcs.Task;
    }
    

    but how to unregister it?
    I don't want to register an handler everytime my command is executed

  • ylemsoulylemsoul RUMember ✭✭✭
    edited August 2015

    You don't need to unregister handlers if you use transient instances of the generated proxy classes (like in the WebClient example) which I would recommend instead of using singletons.


    public static Task<bool> LoginAsync(string Username, string Password) { var tcs = new TaskCompletionSource<string>(); var wc = new HelperClass.ClientWS(); wc.LoginCompleted += (s,e) => { if (e.Error != null) tcs.TrySetException(e.Error); else if (e.Cancelled) tcs.TrySetCanceled(); else tcs.TrySetResult(e.Result); }; wc.LoginAsync(Username, Password); return tcs.Task; }

    If you use single instance of the proxy class then you need to manage state somewhere and reset TaskCompletionSource < T > instance before each call and ensure about single subscription for the handler.

  • CaioshinCaioshin ITMember ✭✭
    edited August 2015

    ok thank you, your example is clear.
    I've a couple of questions about:

    • using transient instances doesn't impacts on performances? My singleton is an instance of a soap client, isn't better to have only 1 instead to continue to allocate and destroy objects (especially on device like smartphones where I could have limited resources)?
    • what is the real benefit to use an implementation like this where my command call a method that return a task object instead of call directly the async service managing the callback like in my original example?

    Thank you

  • CaioshinCaioshin ITMember ✭✭
    edited August 2015

    Another doubt, I tried to implement the solution as suggested, but now I see that my UI is blocked waiting the response from Async Login call.
    Calling AsyncLogin inside the command block the UI thread, I should probably await it?

  • ylemsoulylemsoul RUMember ✭✭✭
    edited August 2015

    @Caioshin, transient instances is slower than using singleton but if you don't do logins hundreds of times per sec then I doubt you will see any difference. It slower, but it takes less memory also because when transient instance is out of scope it will be GC'd eventually.
    But the main reasons for this case are:

    • TAP is more readable and human-friendly with async/await support. It almost as good as synchronous version.
    • you don't need to manage state, so your code will be more maintainable and less complicated. For example, as far as I remember if your proxy is in the Faulted state you can't do any calls further. So you need to handle this also.
  • ylemsoulylemsoul RUMember ✭✭✭
    edited August 2015

    I was wrong in the first comment that event callback is executed on the background thread. Just checked - tool generates proxy that uses System.Threading.SendOrPostCallback to call back to the caller. So actually it dispatches on the UI thread.. Can you check this? Did Device.BeginInvokeOnMainThread(() => MessagingCenter.Send(..)); helped?

  • CaioshinCaioshin ITMember ✭✭

    @ylemsoul yes send the message with BeginInvoke help me, with this the message is received and the app navigate to the new page

  • ylemsoulylemsoul RUMember ✭✭✭
    edited August 2015

    Well, ok.. I don't know what generate your proxy classes.
    Then answering on this:

    Calling AsyncLogin inside the command block the UI thread, I should probably await it?

    Yes, you need to await so you don't block UI:
    Loading = true; bool result = await LoginAsync(username, password); Loading = false; if (result) MessagingCenter.Send(...)

  • DonCB2BDonCB2B USMember ✭✭✭
    edited January 2018

    @ylemsoul Device.BeginInvokeOnMainThread(() => MessagingCenter.Send(..)) helped my problem. Basically, the messaging center not work properly after i put it to background mode, by invoking main thread help solve problem.

Sign In or Register to comment.