How to Dismiss UserDialogs' AlertAsync()

DFoulkDFoulk USMember ✭✭

I am trying to figure out how to "dismiss" an AsyncAlert(), but I run into fatal exception(s) when .Cancel() is called. I have never worked with CancellationTokens before, so I'm likely doing something wrong...

I have been searching for a solution to this issue for hours and I have hit that awesome Friday @ 4:30pm wall of defeat... Any help would be greatly appreciated!

Here is the class that contains the AlertAsync() call:

using Acr.UserDialogs;
using Plugin.Connectivity;
using System;
using System.Threading;

namespace AC_Status.Helpers
{
    public class Network
    {
        private static readonly Lazy<Network> _instance = new Lazy<Network>(() => new Network());

        private CancellationTokenSource _ctx;

        private Network()
        {
            _ctx = new CancellationTokenSource();
        }

        public static Network Current => _instance.Value;

        public bool ConnectivityAlertIsDisplayed { get; set; }

        public bool IsConnected
        {
            get
            {
                if (!CrossConnectivity.IsSupported)
                    return true;

                return CrossConnectivity.Current.IsConnected;
            }
        }

        public async void TestConnectivity()
        {
            if (!ConnectivityAlertIsDisplayed)
            {
                if (!IsConnected)
                {
                    ConnectivityAlertIsDisplayed = true;

                    await UserDialogs.Instance.AlertAsync("Internet Connection Required", "Please establish an internet connection and try again!", "Okay", _ctx?.Token);

                    ConnectivityAlertIsDisplayed = false;
                }
            }
            else if (IsConnected)
            {
                _ctx?.Cancel();
            }
        }
    }
}

The above helper's TestConnectivity() method is called like so:

using System;
using FreshMvvm;
using AC_Status.Helpers;

namespace AC_Status.PageModels
{
    public class BasePageModel : FreshBasePageModel
    {
        public string Title { get; set; }

        public bool IsBusy { get; set; }

        public bool IsInitialized { get; set; }

        protected override void ViewIsAppearing(object sender, EventArgs e)
        {
            base.ViewIsAppearing(sender, e);

            Network.Current.TestConnectivity();
        }
    }
}

Here is the stack trace:

09-08 16:20:10.096 I/MonoDroid(29324): UNHANDLED EXCEPTION:
09-08 16:20:10.103 I/MonoDroid(29324): System.Threading.Tasks.TaskCanceledException: A task was canceled.
09-08 16:20:10.103 I/MonoDroid(29324):   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x00026] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
09-08 16:20:10.103 I/MonoDroid(29324):   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
09-08 16:20:10.103 I/MonoDroid(29324):   at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
09-08 16:20:10.103 I/MonoDroid(29324):   at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
09-08 16:20:10.103 I/MonoDroid(29324):   at Acr.UserDialogs.AbstractUserDialogs+<AlertAsync>d__21.MoveNext () [0x000bb] in C:\dev\acr\userdialogs\src\Acr.UserDialogs.Interface\AbstractUserDialogs.cs:133 
09-08 16:20:10.103 I/MonoDroid(29324): --- End of stack trace from previous location where exception was thrown ---
09-08 16:20:10.103 I/MonoDroid(29324):   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
09-08 16:20:10.103 I/MonoDroid(29324):   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0001a] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
09-08 16:20:10.103 I/MonoDroid(29324):   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
09-08 16:20:10.103 I/MonoDroid(29324):   at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
09-08 16:20:10.103 I/MonoDroid(29324):   at System.Runtime.CompilerServices.TaskAwaiter.GetResult () [0x00000] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
09-08 16:20:10.103 I/MonoDroid(29324):   at AC_Status.Helpers.Network+<TestConnectivity>d__11.MoveNext () [0x0008f] in C:\Users\dfoulk\Repos\AC-Status\AC-Status\AC_Status\Helpers\Network.cs:43 
09-08 16:20:10.103 I/MonoDroid(29324): --- End of stack trace from previous location where exception was thrown ---
09-08 16:20:10.103 I/MonoDroid(29324):   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
09-08 16:20:10.103 I/MonoDroid(29324):   at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__6_0 (System.Object state) [0x00000] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
09-08 16:20:10.103 I/MonoDroid(29324):   at Android.App.SyncContext+<>c__DisplayClass2_0.<Post>b__0 () [0x00000] in <d278c06ad5684d6882c743a94a93ebc2>:0 
09-08 16:20:10.103 I/MonoDroid(29324):   at Java.Lang.Thread+RunnableImplementor.Run () [0x00008] in <d278c06ad5684d6882c743a94a93ebc2>:0 
09-08 16:20:10.103 I/MonoDroid(29324):   at Java.Lang.IRunnableInvoker.n_Run (System.IntPtr jnienv, System.IntPtr native__this) [0x00008] in <d278c06ad5684d6882c743a94a93ebc2>:0 
09-08 16:20:10.103 I/MonoDroid(29324):   at (wrapper dynamic-method) System.Object:20a5f4cd-fc9c-4220-baa8-b833dbf24fb8 (intptr,intptr)
09-08 16:20:10.127 W/art     (29324): JNI RegisterNativeMethods: attempt to register 0 native methods for android.runtime.JavaProxyThrowable
An unhandled exception occured.

09-08 16:20:10.868 E/mono    (29324): 
09-08 16:20:10.868 E/mono    (29324): Unhandled Exception:
09-08 16:20:10.868 E/mono    (29324): System.Threading.Tasks.TaskCanceledException: A task was canceled.
09-08 16:20:10.869 E/mono-rt (29324): [ERROR] FATAL UNHANDLED EXCEPTION: System.Threading.Tasks.TaskCanceledException: A task was canceled.
09-08 16:20:10.893 I/TMSDISP (29324): AcsAndroidVirtualDisplayIntfImpl::~AcsAndroidVirtualDisplayIntfImpl - Enter
09-08 16:20:10.893 I/TMSDISP (29324): AcsAndroidVirtualDisplayIntfImpl::~AcsAndroidVirtualDisplayIntfImpl - Enter2
09-08 16:20:10.893 I/TMSDISP (29324): AcsAndroidVirtualDisplayIntfImpl::~AcsAndroidVirtualDisplayIntfImpl - mSource2
09-08 16:20:10.893 I/TMSDISP (29324): AcsAndroidVirtualDisplayIntfImpl::~AcsAndroidVirtualDisplayIntfImpl - Exit

Thank you in advance! @AllanRitchie

Best Answer

  • DFoulkDFoulk USMember ✭✭
    Accepted Answer

    I used this documentation to add a CancellationToken to my task (and handle cancellation(s)). Hope this helps the next guy!

Answers

  • MarcMeier.1870MarcMeier.1870 USMember ✭✭

    Not very sure what you are trying to do exactly but if you try to check network state why don't you use ConnectivityManager ?

  • MarcMeier.1870MarcMeier.1870 USMember ✭✭

    It would give something like this (fired any time network state changes) (I used an alert dialog instead).

    public class MyNetworkStateReceiver :BroadcastReceiver
    {
    public static bool bHasNetwork = false;

        public override void OnReceive(Context context, Intent intent)
        {            
    
            ConnectivityManager conmgr = (ConnectivityManager)context.GetSystemService(Context.ConnectivityService);
            bool bNetWorkFound = conmgr.ActiveNetwork != null;
    
            var network = conmgr.ActiveNetwork;
            bNetWorkFound = network != null;
            }
    
        if(!bNetWorkFound)
    

    {
    var alert = new AlertDialog.Builder(activity);
    alert.SetTitle("Internet Connection Required");
    alert.SetMessage("Please establish an internet connection and try again!");

                        alert.SetPositiveButton("OKay", (sender, args) => { DoWhateverYouWant(); });
                        alert.SetNegativeButton("Cancel", (sender, args) => { YourCancelation(); });
    

    }

    }

    Let me know...

  • DFoulkDFoulk USMember ✭✭

    @MarcMeier.1870 - I am trying to programatically close an alert :) I apologize for leaving my internet connectivity test in the code, but I did so to provide context.

    Essentially, I show an alert when the internet connectivity test fails and I would like to close that alert when the internet connectivity succeeds (these checks occur OnAppearing).

  • MarcMeier.1870MarcMeier.1870 USMember ✭✭

    I personally use alert only when I need a decision from the user or for a warning (in this case I want a confirmation that the user is informed, so I need an answer as well). If no interaction is needed I prefer to use progress dialog.
    So in order to keep a simple code I would use a progress dialog while you are trying to establish a connection.
    Dismiss it if you get it or if it fails and you need the user to decide to try again or cancel include an alert only at that time.

    Something like this (just change the code in order to make a loop when user want to try again. this is just to give you an idea...):

    ProgressDialog progress = ProgressDialog.Show(activity,"Internet Connection Required" , "Trying to establish an internet connection", true);

            new Thread(new ThreadStart(() =>
            {
                bool bError = GetYourConnectionTestResult();
                activity.RunOnUiThread(() =>
                {
                    if (bError)
                    {
                        var alert = new AlertDialog.Builder(activity);
                        alert.SetTitle("Internet Connection Required");
                            alert.SetMessage("Please establish an internet connection and try again!");
                        alert.SetPositiveButton("Try again", (senderAlert, args) => {  TryAgainToGetAConnection(); });
                        alert.SetNegativeButton("Cancel", (senderAlert, args) => {  progress.Dismiss(); });
                        activity.RunOnUiThread(() => { alert.Show(); });
                    }
                    else
                        progress.Dismiss();
                });
            })).Start();
    

    Hoping this would help you...

  • DFoulkDFoulk USMember ✭✭

    @MarcMeier.1870 said:
    I personally use alert only when I need a decision from the user or for a warning (in this case I want a confirmation that the user is informed, so I need an answer as well). If no interaction is needed I prefer to use progress dialog.
    So in order to keep a simple code I would use a progress dialog while you are trying to establish a connection.
    Dismiss it if you get it or if it fails and you need the user to decide to try again or cancel include an alert only at that time.

    Something like this (just change the code in order to make a loop when user want to try again. this is just to give you an idea...):

    ProgressDialog progress = ProgressDialog.Show(activity,"Internet Connection Required" , "Trying to establish an internet connection", true);

    new Thread(new ThreadStart(() =>
    {
    bool bError = GetYourConnectionTestResult();
    activity.RunOnUiThread(() =>
    {
    if (bError)
    {
    var alert = new AlertDialog.Builder(activity);
    alert.SetTitle("Internet Connection Required");
    alert.SetMessage("Please establish an internet connection and try again!");
    alert.SetPositiveButton("Try again", (senderAlert, args) => { TryAgainToGetAConnection(); });
    alert.SetNegativeButton("Cancel", (senderAlert, args) => { progress.Dismiss(); });
    activity.RunOnUiThread(() => { alert.Show(); });
    }
    else
    progress.Dismiss();
    });
    })).Start();

    Hoping this would help you...

    That's a valid approach. This is pretty much what I'm trying to accomplish, but there is no Dismiss() method on the Xamarin.Forms alert... I suppose I can try using a DependencyService and implement something native on each platform.

    My only hesitation in doing so is that there is a Dismiss() equivalent for Acr.UserDialogs.AlertAsync() (Xamarin.Forms compatible).

    I did find something regarding CancellationTokens and awaitable methods (that I can't find now/can't remember) that is probably the cause of the error I'm encountering. I'm gonna start with a fresh palette and try to eliminate the need for the asynchronous alert, then I can use something different (i.e. Acr.UserDialogs > Alert, which implements IDisposable).

    Thank you for the assistance!

  • DFoulkDFoulk USMember ✭✭
    Accepted Answer

    I used this documentation to add a CancellationToken to my task (and handle cancellation(s)). Hope this helps the next guy!

  • AllanRitchieAllanRitchie CAInsider, University ✭✭✭

    @DFoulk If you need to cancel only the dialog, you are better to use the standard versions of the call. The sync versions do have callback functions in them. I only use the async versions if I'm doing other async along side the dialog that also needs to be cancelled. That way, there isn't a need to trap the AggregatedException for nothing more than a dialog.

  • DFoulkDFoulk USMember ✭✭

    @AllanRitchie Thank you for your reply! I am using the asynchronous version for other reasons (not cancellation). I can't (for the life of me) remember why- but I think it was to do something after the alert was dismissed (without stalling the UI thread). If that is the case- I could probably rewrite the alert to the synchronous version (worth it?).

    That being said- I do believe the cancellation/token was needed just to cancel the alert... So maybe I can revisit this. Where can I read more about the callback functions (and how to implement them)?

    Thank you again!
    -Derek

  • DFoulkDFoulk USMember ✭✭

    By the way- thank you for your plugins. They are great!

  • AllanRitchieAllanRitchie CAInsider, University ✭✭✭

    The dialogs won't freeze your UI thread. They will render on it obviously, for the split second it takes to display the dialog itself. The wait for the user interaction won't block though. For docs, go to the source, more specifically - the samples. All you seek is here https://github.com/aritchie/userdialogs/blob/master/src/Samples/Samples/ViewModels/StandardViewModel.cs

    Glad the plugins are helping.

Sign In or Register to comment.