Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TaskCompletionSource and dismiss causes that multiple custom alertdialogs aren't appearing

I got a problem related with the TaskCompletionSource object and the Dismiss function of an alert. This problem does not appear within the IOS version of the application

When the application sends an notification, two alerts with working functionality will show up when the user activates the application:

  1. Authentication
  2. Filling in a value.

However, when I enter the application I only get to see the authentication(because this alert is called first within the app) and the second alert never shows up. I already tried to override the Dismiss function and set the TaskCompletionSource object result to null, but this causes the same alert to appear an X times before the application crashes. Is there a way to repeat the TaskCompletionSource object so that I can see all the alerts? Or what kind of modifications do I need to make on the Dismiss function so that the TaskCompletionSource is completed once all the alerts are shown?

Android Fragment Example code:

public static readonly int AlertWidth = Device.Idiom == TargetIdiom.Phone ? 270 : 320;

    class AlertDialogFragment : DialogFragment
    {
        public string Title;
        public string Body;
        public View Content;
        public List<AlertButton> Buttons;
        public TaskCompletionSource<object> tsc;


        public Dialog AndroidCustomAlert(Activity activ)
        {
            Android.Views.LayoutInflater inflater = Android.Views.LayoutInflater.From(activ);
            Android.Views.View view = inflater.Inflate(Resource.Layout.AlertDialogLayout, null);

            AlertDialog.Builder builder = new AlertDialog.Builder(activ);
            builder.SetView(view);
            Android.Widget.TextView title = view.FindViewById<Android.Widget.TextView>(Resource.Id.Login);
            title.Text = Title;

            Android.Widget.TextView body = view.FindViewById<Android.Widget.TextView>(Resource.Id.pincodeText);
            body.Text = Body;
            body.MovementMethod = new Android.Text.Method.ScrollingMovementMethod();

            Android.Widget.EditText pincode = view.FindViewById<Android.Widget.EditText>(Resource.Id.pincodeEditText);
            Android.Widget.Button btnPositive = view.FindViewById<Android.Widget.Button>(Resource.Id.btnLoginLL);
            Android.Widget.Button btnNegative = view.FindViewById<Android.Widget.Button>(Resource.Id.btnClearLL);
            Android.Widget.Button btnNeutral = view.FindViewById<Android.Widget.Button>(Resource.Id.btnNeutral);

            if (Title.Contains("Time"))
            {
                Android.Views.View secondView = inflater.Inflate(Resource.Layout.TimePickerLayout, null);
                builder.SetView(secondView);

                btnPositive = secondView.FindViewById<Android.Widget.Button>(Resource.Id.btnLoginLL);
                btnNegative = secondView.FindViewById<Android.Widget.Button>(Resource.Id.btnClearLL);
                var tp = secondView.FindViewById<Android.Widget.TimePicker>(Resource.Id.timePicker1);
                tp.SetIs24HourView((Java.Lang.Boolean)true);
                //Positive button feedback
                btnPositive.Text = Buttons.Last().Text;
                btnPositive.Click += delegate
                {
                    var car = (Xamarin.Forms.TimePicker)Content;
                    var ts = new TimeSpan(tp.Hour, tp.Minute, 0);
                    car.Time = ts;

                    CommandsForButtons(Buttons.Last());
                };

                //Negative button feedback
                btnNegative.Text = Buttons.First().Text;
                btnNegative.Click += delegate
                {

                    CommandsForButtons(Buttons.First());
                };
            }
            else if (Title.Contains("How are you"))
            {
                btnPositive.Visibility = Android.Views.ViewStates.Gone;
                btnNegative.Visibility = Android.Views.ViewStates.Gone;
                btnNeutral.Visibility = Android.Views.ViewStates.Visible;
                pincode.Visibility = Android.Views.ViewStates.Gone;

                var happySlider = view.FindViewById<Android.Widget.SeekBar>(Resource.Id.happinessSlider);
                happySlider.SetProgress(5, false);
                happySlider.Visibility = Android.Views.ViewStates.Visible;
                btnNeutral.Text = Buttons.First().Text;
                btnNeutral.Click += delegate
                {
                    var car = (StackLayout)Content;

                    var layoutView = (Xamarin.Forms.AbsoluteLayout)car.Children[1];
                    var slider = (Slider)layoutView.Children[1];

                    var totalHappyValue = happySlider.Progress / 10;
                    slider.Value = totalHappyValue;

                    CommandsForButtons(Buttons.First());
                };
            }
            else
            {

                //Checks if there are no buttons, and if there aren't any, creates a neutral one
                if (Buttons == null || Buttons.Count == 0)
                {
                    btnPositive.Visibility = Android.Views.ViewStates.Gone;
                    btnNegative.Visibility = Android.Views.ViewStates.Gone;
                    btnNeutral.Visibility = Android.Views.ViewStates.Visible;
                    pincode.Visibility = Android.Views.ViewStates.Gone;

                    Buttons = new List<AlertButton> {
                new AlertButton {
                    Text = "Oké",
                    IsPreferred = true,
                    Action = () => false
                    }
                };
                    btnNeutral.Text = Buttons.First().Text;
                    btnNeutral.Click += delegate
                    {

                        CommandsForButtons(Buttons.First());
                    };
                }

                if (Content == null)
                {
                    pincode.Visibility = Android.Views.ViewStates.Gone;
                }

                //Positive button feedback
                btnPositive.Text = Buttons.Last().Text;
                btnPositive.Click += delegate
                {
                    var test = (StackLayout)Content;

                    if (test != null)
                    {
                        var car = (Entry)test.Children[0];
                        car.Text = pincode.Text;
                    }

                    CommandsForButtons(Buttons.Last());
                };

                //Negative button feedback
                btnNegative.Text = Buttons.First().Text;
                btnNegative.Click += delegate
                {

                    CommandsForButtons(Buttons.First());
                };
            }
            return builder.Create();
        }

        public void CommandsForButtons(AlertButton button)
        {
            Func<Task> dismiss = null;
            var command = new Command(async () =>
            {
                var ab = button;
                var cont = true;
                if (ab.Action != null)
                    cont = ab.Action();
                if (ab.ActionAsync != null)
                {
                    cont = cont && await ab.ActionAsync();
                }
                if (!cont)
                {
                    await dismiss();
                }
            });

            dismiss = async () =>
            {
                dismiss = async () => { };
                await Task.Run(() =>
                {
                    Dismiss();
                    tsc.SetResult(null);
                });

                Log.Debug("TSC", tsc.Task.Status.ToString());

            };

            command.Execute(this);
        }

        public override Dialog OnCreateDialog(Bundle savedInstanceState)
        {
            var test = AndroidCustomAlert(Activity);
            test.SetCanceledOnTouchOutside(false);
            return test;
        }

        public override void Dismiss()
        {
            base.Dismiss();

        }
    }

    public async Task Show(string title, string body, View content, List<AlertButton> buttons)
    {

        var tcs = new TaskCompletionSource<object>();

        var adf = new AlertDialogFragment
        {
            Title = title,
            Body = body,
            Content = content,
            Buttons = buttons,
            tsc = tcs
        };
        var FragmentManager = ((Activity)Forms.Context).FragmentManager;
        FragmentTransaction ft = FragmentManager.BeginTransaction();

        //Remove fragment else it will crash as it is already added to backstack
        Fragment prev = FragmentManager.FindFragmentByTag("alert");
        if (prev != null)
        {
            ft.Remove(prev);
        }

        ft.AddToBackStack(null);
        adf.Show(ft, "alert");
        await tcs.Task;
    }

The methods:

await Authentication();
await UserCheck();

And the IOS code:

public static readonly int AlertWidth = Device.Idiom == TargetIdiom.Phone ? 270 : 320;

    public async Task Show(string title, string body, View content, List<AlertButton> buttons)
    {
        if (buttons == null || buttons.Count == 0)
        {
            buttons = new List<AlertButton> {
                new AlertButton {
                    Text = "Oké",
                    IsPreferred = true,
                    Action = () => false
                }
            };
        }

        Func<Task> dismiss = null;

        var captionSize = (double)StyleKit.PhoneDarkLabelStyles.Caption.Setters.First(s => s.Property == Label.FontSizeProperty).Value;
        var titleSize = (double)StyleKit.PhoneDarkLabelStyles.Title.Setters.First(s => s.Property == Label.FontSizeProperty).Value;

        var top = new StackLayout {
            Padding = new Thickness(15, 20, 15, 20),
            Spacing = 3,
            Children = {
                new Label {
                    Text = title,
                    Style = StyleKit.PhoneDarkLabelStyles.Title,
                    FontSize = Math.Max(16, titleSize),
                    HorizontalTextAlignment = TextAlignment.Center
                },
                new Label {
                    Text = body,
                    Style = StyleKit.PhoneDarkLabelStyles.Body,
                    //FontSize = ,
                    FontSize = Math.Max(14, captionSize),
                    HorizontalTextAlignment = TextAlignment.Center
                } ,
                new ContentView {
                    Padding = new Thickness(0,5,0,-10),
                    VerticalOptions = LayoutOptions.EndAndExpand,
                    Content = content
                } 
            }
        };

        var buttonViews = buttons.Select(ab => new Button {
            FontSize = Math.Max(16, titleSize),
            Text = ab.Text,
            FontAttributes = ab.IsPreferred ? FontAttributes.Bold : FontAttributes.None,
            TextColor = ab.IsDestructive ? Color.Red : Color.Default,
            Command = new Command(async () => {
                var cont = true;
                if (ab.Action != null)
                    cont = ab.Action();
                if (ab.ActionAsync != null)
                    cont = cont && await ab.ActionAsync();
                if (!cont)
                    await dismiss();
            })
        }).ToList();

        var grid = new Grid {
            RowDefinitions = {
                new RowDefinition { Height = GridLength.Auto },
                new RowDefinition { Height = GridLength.Auto }
            },
            ColumnSpacing = 0,
            RowSpacing = 0
        };
        buttons.ForEach(button => {
            grid.ColumnDefinitions.Add(
                new ColumnDefinition {
                    Width = AlertWidth / buttonViews.Count
                }
            );
        });

        for (int i = 0; i < buttonViews.Count; i++)
        {
            grid.Children.Add(new BorderView {
                BorderColor = Color.FromRgba(0,0,0,0.2),
                Thickness = new Thickness(0, 1, (i + 1 < buttonViews.Count) ? 1 : 0, 0)
            }, i, 1);
            grid.Children.Add(buttonViews[i], i, 1);
        }
        grid.Children.Add(top, 0, buttons.Count, 0, 1);

        var box = new Frame {
            WidthRequest = AlertWidth,
            BackgroundColor = Color.FromRgba(1,1,1,0.96),
            Padding = 0,
            Content = grid
        };
        var outer = new AbsoluteLayout {
            BackgroundColor = Color.FromRgba(0,0,0,0.65),
            Opacity = 0,
            Children = { box }
        };
        AbsoluteLayout.SetLayoutFlags(box, AbsoluteLayoutFlags.PositionProportional);
        AbsoluteLayout.SetLayoutBounds(box,
            new Rectangle(0.5, 0.5, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));

        var page = new ContentPage {
            Content = /* new ScrollView { Content = */ outer // }
        };

        var tcs = new TaskCompletionSource<object>();

        var topVC = UIApplication.SharedApplication.KeyWindow.RootViewController;
        while (topVC.PresentedViewController != null) {
            topVC = topVC.PresentedViewController;
        }

        var vc = page.CreateViewController();
        topVC.Add(vc.View);
        var innerView = vc.View.Subviews[0].Subviews[0];
        vc.View.RemoveFromSuperview();

        dismiss = async () => {
            dismiss = async () => {};
            await outer.FadeTo(0, 50);
            innerView.RemoveFromSuperview();
            tcs.SetResult(null);
        };

        topVC.Add(innerView);

        var kbh = new KeyboardHelper();
        kbh.KeyboardChanged += async (sender, e) => {
            await box.TranslateTo(0, e.Visible ? (-e.Height / 2f) : 0, 100, Easing.CubicInOut);
        };

        await outer.FadeTo(1, 100);

        await tcs.Task;
    }
like image 789
frederick van der meulen Avatar asked May 28 '17 15:05

frederick van der meulen


1 Answers

This looks like an and/or problem between threading or simply not setting the task completion.

In the IOS version you have

     dismiss = async () => {
        dismiss = async () => {};
        await outer.FadeTo(0, 50);
        innerView.RemoveFromSuperview();
        tcs.SetResult(null);
    };

There is no matching tcs.SetResult(null); statement in the android version.

Also of note is the async invocation, as waiting to call SetResult will block the main thread without it.

like image 91
CCondron Avatar answered Oct 31 '22 20:10

CCondron