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:
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;
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With