From 6781c1bf73b2181b9eb91fc516a36eabc9e83959 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Wed, 10 Mar 2021 11:08:22 -0800
Subject: [PATCH 1/2] Implement Safe-Fire-And-Forget
---
.../Effects/Touch/GestureManager.shared.cs | 27 +++----
.../Touch/PlatformTouchEffect.android.cs | 52 +++++++-------
.../Effects/Touch/PlatformTouchEffect.ios.cs | 34 +++++----
.../Touch/PlatformTouchEffect.macos.cs | 22 +++---
.../Touch/PlatformTouchEffect.tizen.cs | 37 +++++-----
.../Effects/Touch/TouchEffect.shared.cs | 30 ++++----
.../SafeFireAndForgetExtensions.shared.cs | 70 +++++++++++++++++++
.../Internals/BaseAsyncCommand.shared.cs | 8 +--
.../Internals/BaseAsyncValueCommand.shared.cs | 8 +--
.../Android/FormsVideoView.android.cs | 4 +-
10 files changed, 180 insertions(+), 112 deletions(-)
create mode 100644 src/CommunityToolkit/Xamarin.CommunityToolkit/Helpers/SafeFireAndForgetExtensions.shared.cs
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/GestureManager.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/GestureManager.shared.cs
index ebb2ee306..3cfbc78ba 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/GestureManager.shared.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/GestureManager.shared.cs
@@ -4,6 +4,7 @@
using System.Threading;
using System.Threading.Tasks;
using Xamarin.CommunityToolkit.Extensions;
+using Xamarin.CommunityToolkit.Helpers;
using Xamarin.Forms;
using static System.Math;
@@ -27,7 +28,7 @@ sealed class GestureManager
TouchState animationState;
- internal async Task HandleTouch(TouchEffect sender, TouchStatus status)
+ internal void HandleTouch(TouchEffect sender, TouchStatus status)
{
if (sender.IsDisabled)
return;
@@ -58,11 +59,11 @@ internal async Task HandleTouch(TouchEffect sender, TouchStatus status)
? 1 - animationProgress
: animationProgress;
- await UpdateStatusAndState(sender, status, state);
+ UpdateStatusAndState(sender, status, state);
if (status == TouchStatus.Canceled)
{
- await sender.ForceUpdateState(false);
+ sender.ForceUpdateState(false);
return;
}
@@ -76,7 +77,7 @@ internal async Task HandleTouch(TouchEffect sender, TouchStatus status)
: TouchState.Pressed;
}
- await UpdateStatusAndState(sender, status, state);
+ UpdateStatusAndState(sender, status, state);
}
if (status == TouchStatus.Completed)
@@ -92,7 +93,7 @@ internal void HandleUserInteraction(TouchEffect sender, TouchInteractionStatus i
}
}
- internal async ValueTask HandleHover(TouchEffect sender, HoverStatus status)
+ internal void HandleHover(TouchEffect sender, HoverStatus status)
{
if (!sender.Element?.IsEnabled ?? true)
return;
@@ -104,7 +105,7 @@ internal async ValueTask HandleHover(TouchEffect sender, HoverStatus status)
if (sender.HoverState != hoverState)
{
sender.HoverState = hoverState;
- await sender.RaiseHoverStateChanged();
+ sender.RaiseHoverStateChanged();
}
if (sender.HoverStatus != status)
@@ -140,7 +141,7 @@ internal async Task ChangeStateAsync(TouchEffect sender, bool animated)
var durationMultiplier = this.durationMultiplier;
this.durationMultiplier = null;
- await GetAnimationTask(sender, state, hoverState, animationTokenSource.Token, durationMultiplier.GetValueOrDefault()).ConfigureAwait(false);
+ await RunAnimationTask(sender, state, hoverState, animationTokenSource.Token, durationMultiplier.GetValueOrDefault()).ConfigureAwait(false);
return;
}
@@ -148,7 +149,7 @@ internal async Task ChangeStateAsync(TouchEffect sender, bool animated)
if (pulseCount == 0 || (state == TouchState.Normal && !isToggled.HasValue))
{
- await GetAnimationTask(sender, state, hoverState, animationTokenSource.Token).ConfigureAwait(false);
+ await RunAnimationTask(sender, state, hoverState, animationTokenSource.Token).ConfigureAwait(false);
return;
}
do
@@ -157,7 +158,7 @@ internal async Task ChangeStateAsync(TouchEffect sender, bool animated)
? TouchState.Normal
: TouchState.Pressed;
- await GetAnimationTask(sender, rippleState, hoverState, animationTokenSource.Token);
+ await RunAnimationTask(sender, rippleState, hoverState, animationTokenSource.Token);
if (token.IsCancellationRequested)
return;
@@ -165,7 +166,7 @@ internal async Task ChangeStateAsync(TouchEffect sender, bool animated)
? TouchState.Pressed
: TouchState.Normal;
- await GetAnimationTask(sender, rippleState, hoverState, animationTokenSource.Token);
+ await RunAnimationTask(sender, rippleState, hoverState, animationTokenSource.Token);
if (token.IsCancellationRequested)
return;
}
@@ -269,12 +270,12 @@ internal void AbortAnimations(TouchEffect sender)
element.AbortAnimations();
}
- async ValueTask UpdateStatusAndState(TouchEffect sender, TouchStatus status, TouchState state)
+ void UpdateStatusAndState(TouchEffect sender, TouchStatus status, TouchState state)
{
if (sender.State != state || status != TouchStatus.Canceled)
{
sender.State = state;
- await sender.RaiseStateChanged();
+ sender.RaiseStateChanged();
}
sender.Status = status;
@@ -586,7 +587,7 @@ Color GetBackgroundColor(Color color)
? color
: defaultBackgroundColor;
- Task GetAnimationTask(TouchEffect sender, TouchState touchState, HoverState hoverState, CancellationToken token, double? durationMultiplier = null)
+ Task RunAnimationTask(TouchEffect sender, TouchState touchState, HoverState hoverState, CancellationToken token, double? durationMultiplier = null)
{
if (sender.Element == null)
return Task.FromResult(false);
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.android.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.android.cs
index ae0147a2d..e3db9123e 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.android.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.android.cs
@@ -1,6 +1,5 @@
using System;
using System.ComponentModel;
-using System.Threading.Tasks;
using Android.Content;
using Android.Content.Res;
using Android.Graphics.Drawables;
@@ -12,7 +11,6 @@
using Xamarin.CommunityToolkit.Effects;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
-using AndroidOS = Android.OS;
using AView = Android.Views.View;
using Color = Android.Graphics.Color;
@@ -161,7 +159,7 @@ void UpdateClickHandler()
}
}
- async void OnTouch(object? sender, AView.TouchEventArgs e)
+ void OnTouch(object? sender, AView.TouchEventArgs e)
{
e.Handled = false;
@@ -174,47 +172,51 @@ async void OnTouch(object? sender, AView.TouchEventArgs e)
switch (e.Event?.ActionMasked)
{
case MotionEventActions.Down:
- await OnTouchDown(e);
+ OnTouchDown(e);
break;
case MotionEventActions.Up:
- await OnTouchUp();
+ OnTouchUp();
break;
case MotionEventActions.Cancel:
- await OnTouchCancel();
+ OnTouchCancel();
break;
case MotionEventActions.Move:
- await OnTouchMove(sender, e);
+ OnTouchMove(sender, e);
break;
case MotionEventActions.HoverEnter:
- await OnHoverEnter();
+ OnHoverEnter();
break;
case MotionEventActions.HoverExit:
- await OnHoverExit();
+ OnHoverExit();
break;
}
}
- async Task OnTouchDown(AView.TouchEventArgs e)
+ void OnTouchDown(AView.TouchEventArgs e)
{
_ = e.Event ?? throw new NullReferenceException();
IsCanceled = false;
+
startX = e.Event.GetX();
startY = e.Event.GetY();
+
effect?.HandleUserInteraction(TouchInteractionStatus.Started);
- await (effect?.HandleTouch(TouchStatus.Started) ?? Task.CompletedTask);
+ effect?.HandleTouch(TouchStatus.Started);
+
StartRipple(e.Event.GetX(), e.Event.GetY());
+
if (effect?.DisallowTouchThreshold > 0)
Group?.Parent?.RequestDisallowInterceptTouchEvent(true);
}
- Task OnTouchUp()
+ void OnTouchUp()
=> HandleEnd(effect?.Status == TouchStatus.Started ? TouchStatus.Completed : TouchStatus.Canceled);
- Task OnTouchCancel()
+ void OnTouchCancel()
=> HandleEnd(TouchStatus.Canceled);
- async Task OnTouchMove(object? sender, AView.TouchEventArgs e)
+ void OnTouchMove(object? sender, AView.TouchEventArgs e)
{
if (IsCanceled || e.Event == null)
return;
@@ -226,7 +228,7 @@ async Task OnTouchMove(object? sender, AView.TouchEventArgs e)
var disallowTouchThreshold = effect?.DisallowTouchThreshold;
if (disallowTouchThreshold > 0 && maxDiff > disallowTouchThreshold)
{
- await HandleEnd(TouchStatus.Canceled);
+ HandleEnd(TouchStatus.Canceled);
return;
}
@@ -239,11 +241,11 @@ async Task OnTouchMove(object? sender, AView.TouchEventArgs e)
if (isHoverSupported && ((status == TouchStatus.Canceled && effect?.HoverStatus == HoverStatus.Entered)
|| (status == TouchStatus.Started && effect?.HoverStatus == HoverStatus.Exited)))
- await effect.HandleHover(status == TouchStatus.Started ? HoverStatus.Entered : HoverStatus.Exited);
+ effect.HandleHover(status == TouchStatus.Started ? HoverStatus.Entered : HoverStatus.Exited);
if (effect?.Status != status)
{
- await (effect?.HandleTouch(status) ?? Task.CompletedTask);
+ effect?.HandleTouch(status);
if (status == TouchStatus.Started)
StartRipple(e.Event.GetX(), e.Event.GetY());
@@ -252,23 +254,23 @@ async Task OnTouchMove(object? sender, AView.TouchEventArgs e)
}
}
- async ValueTask OnHoverEnter()
+ void OnHoverEnter()
{
isHoverSupported = true;
if (effect != null)
- await effect.HandleHover(HoverStatus.Entered);
+ effect.HandleHover(HoverStatus.Entered);
}
- async ValueTask OnHoverExit()
+ void OnHoverExit()
{
isHoverSupported = true;
if (effect != null)
- await effect.HandleHover(HoverStatus.Exited);
+ effect.HandleHover(HoverStatus.Exited);
}
- async void OnClick(object? sender, EventArgs args)
+ void OnClick(object? sender, EventArgs args)
{
if (effect?.IsDisabled ?? true)
return;
@@ -277,10 +279,10 @@ async void OnClick(object? sender, EventArgs args)
return;
IsCanceled = false;
- await HandleEnd(TouchStatus.Completed);
+ HandleEnd(TouchStatus.Completed);
}
- async Task HandleEnd(TouchStatus status)
+ void HandleEnd(TouchStatus status)
{
if (IsCanceled)
return;
@@ -289,7 +291,7 @@ async Task HandleEnd(TouchStatus status)
if (effect?.DisallowTouchThreshold > 0)
Group?.Parent?.RequestDisallowInterceptTouchEvent(false);
- await (effect?.HandleTouch(status) ?? Task.CompletedTask);
+ effect?.HandleTouch(status);
effect?.HandleUserInteraction(TouchInteractionStatus.Completed);
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.ios.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.ios.cs
index ec21c6aa5..0b7d16c59 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.ios.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.ios.cs
@@ -46,7 +46,7 @@ protected override void OnAttached()
if (XCT.IsiOS13OrNewer)
{
- hoverGesture = new UIHoverGestureRecognizer(async () => await OnHover());
+ hoverGesture = new UIHoverGestureRecognizer(OnHover);
View.AddGestureRecognizer(hoverGesture);
}
@@ -79,7 +79,7 @@ protected override void OnDetached()
effect = null;
}
- async ValueTask OnHover()
+ void OnHover()
{
if (effect == null || effect.IsDisabled)
return;
@@ -88,10 +88,10 @@ async ValueTask OnHover()
{
case UIGestureRecognizerState.Began:
case UIGestureRecognizerState.Changed:
- await effect.HandleHover(HoverStatus.Entered);
+ effect.HandleHover(HoverStatus.Entered);
break;
case UIGestureRecognizerState.Ended:
- await effect.HandleHover(HoverStatus.Exited);
+ effect.HandleHover(HoverStatus.Exited);
break;
}
}
@@ -126,38 +126,44 @@ public TouchUITapGestureRecognizer(TouchEffect effect)
UIView? Renderer => (UIView?)effect?.Element.GetRenderer();
- public override async void TouchesBegan(NSSet touches, UIEvent evt)
+ public override void TouchesBegan(NSSet touches, UIEvent evt)
{
if (effect?.IsDisabled ?? true)
return;
IsCanceled = false;
startPoint = GetTouchPoint(touches);
- await HandleTouch(TouchStatus.Started, TouchInteractionStatus.Started);
+
+ HandleTouch(TouchStatus.Started, TouchInteractionStatus.Started).SafeFireAndForget();
+
base.TouchesBegan(touches, evt);
}
- public override async void TouchesEnded(NSSet touches, UIEvent evt)
+ public override void TouchesEnded(NSSet touches, UIEvent evt)
{
if (effect?.IsDisabled ?? true)
return;
- await HandleTouch(effect?.Status == TouchStatus.Started ? TouchStatus.Completed : TouchStatus.Canceled, TouchInteractionStatus.Completed);
+ HandleTouch(effect?.Status == TouchStatus.Started ? TouchStatus.Completed : TouchStatus.Canceled, TouchInteractionStatus.Completed).SafeFireAndForget();
+
IsCanceled = true;
+
base.TouchesEnded(touches, evt);
}
- public override async void TouchesCancelled(NSSet touches, UIEvent evt)
+ public override void TouchesCancelled(NSSet touches, UIEvent evt)
{
if (effect?.IsDisabled ?? true)
return;
- await HandleTouch(TouchStatus.Canceled, TouchInteractionStatus.Completed);
+ HandleTouch(TouchStatus.Canceled, TouchInteractionStatus.Completed).SafeFireAndForget();
+
IsCanceled = true;
+
base.TouchesCancelled(touches, evt);
}
- public override async void TouchesMoved(NSSet touches, UIEvent evt)
+ public override void TouchesMoved(NSSet touches, UIEvent evt)
{
if (effect?.IsDisabled ?? true)
return;
@@ -171,7 +177,7 @@ public override async void TouchesMoved(NSSet touches, UIEvent evt)
var maxDiff = Math.Max(diffX, diffY);
if (maxDiff > disallowTouchThreshold)
{
- await HandleTouch(TouchStatus.Canceled, TouchInteractionStatus.Completed);
+ HandleTouch(TouchStatus.Canceled, TouchInteractionStatus.Completed).SafeFireAndForget();
IsCanceled = true;
base.TouchesMoved(touches, evt);
return;
@@ -183,7 +189,7 @@ public override async void TouchesMoved(NSSet touches, UIEvent evt)
: TouchStatus.Canceled;
if (effect?.Status != status)
- await HandleTouch(status);
+ HandleTouch(status).SafeFireAndForget();
base.TouchesMoved(touches, evt);
}
@@ -216,7 +222,7 @@ public async Task HandleTouch(TouchStatus status, TouchInteractionStatus? intera
interactionStatus = null;
}
- await (effect?.HandleTouch(status) ?? Task.CompletedTask);
+ effect?.HandleTouch(status);
if (interactionStatus.HasValue)
effect?.HandleUserInteraction(interactionStatus.Value);
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.macos.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.macos.cs
index 7c39d83bc..561bfd946 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.macos.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.macos.cs
@@ -77,20 +77,20 @@ public override void UpdateTrackingAreas()
AddTrackingArea(trackingArea);
}
- public override async void MouseEntered(NSEvent theEvent)
+ public override void MouseEntered(NSEvent theEvent)
{
if (effect == null || effect.IsDisabled)
return;
- await effect.HandleHover(HoverStatus.Entered);
+ effect.HandleHover(HoverStatus.Entered);
}
- public override async void MouseExited(NSEvent theEvent)
+ public override void MouseExited(NSEvent theEvent)
{
if (effect == null || effect.IsDisabled)
return;
- await effect.HandleHover(HoverStatus.Exited);
+ effect.HandleHover(HoverStatus.Exited);
}
protected override void Dispose(bool disposing)
@@ -137,18 +137,18 @@ Rectangle ViewRect
}
}
- public override async void MouseDown(NSEvent mouseEvent)
+ public override void MouseDown(NSEvent mouseEvent)
{
if (effect == null || effect.IsDisabled)
return;
effect.HandleUserInteraction(TouchInteractionStatus.Started);
- await effect.HandleTouch(TouchStatus.Started);
+ effect.HandleTouch(TouchStatus.Started);
base.MouseDown(mouseEvent);
}
- public override async void MouseUp(NSEvent mouseEvent)
+ public override void MouseUp(NSEvent mouseEvent)
{
if (effect == null || effect.IsDisabled)
return;
@@ -160,14 +160,14 @@ public override async void MouseUp(NSEvent mouseEvent)
? TouchStatus.Completed
: TouchStatus.Canceled;
- await effect.HandleTouch(status);
+ effect.HandleTouch(status);
}
effect.HandleUserInteraction(TouchInteractionStatus.Completed);
base.MouseUp(mouseEvent);
}
- public override async void MouseDragged(NSEvent mouseEvent)
+ public override void MouseDragged(NSEvent mouseEvent)
{
if (effect == null || effect.IsDisabled)
return;
@@ -176,10 +176,10 @@ public override async void MouseDragged(NSEvent mouseEvent)
if ((status == TouchStatus.Canceled && effect.HoverStatus == HoverStatus.Entered) ||
(status == TouchStatus.Started && effect.HoverStatus == HoverStatus.Exited))
- await effect.HandleHover(status == TouchStatus.Started ? HoverStatus.Entered : HoverStatus.Exited);
+ effect.HandleHover(status == TouchStatus.Started ? HoverStatus.Entered : HoverStatus.Exited);
if (effect.Status != status)
- await effect.HandleTouch(status);
+ effect.HandleTouch(status);
base.MouseDragged(mouseEvent);
}
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.tizen.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.tizen.cs
index 4382ed442..dd71cea77 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.tizen.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/PlatformTouchEffect.tizen.cs
@@ -1,5 +1,4 @@
-using System.Threading.Tasks;
-using ElmSharp;
+using ElmSharp;
using Xamarin.CommunityToolkit.Effects;
using Xamarin.CommunityToolkit.Tizen.Effects;
using Xamarin.Forms;
@@ -51,13 +50,13 @@ sealed class TouchTapGestureRecognizer : GestureLayer
public TouchTapGestureRecognizer(EvasObject parent)
: base(parent)
{
- SetTapCallback(GestureType.Tap, GestureState.Start, async data => await OnTapStarted(data));
- SetTapCallback(GestureType.Tap, GestureState.End, async data => await OnGestureEnded(data));
- SetTapCallback(GestureType.Tap, GestureState.Abort, async data => await OnGestureAborted(data));
+ SetTapCallback(GestureType.Tap, GestureState.Start, OnTapStarted);
+ SetTapCallback(GestureType.Tap, GestureState.End, OnGestureEnded);
+ SetTapCallback(GestureType.Tap, GestureState.Abort, OnGestureAborted);
- SetTapCallback(GestureType.LongTap, GestureState.Start, async data => await OnLongTapStarted(data));
- SetTapCallback(GestureType.LongTap, GestureState.End, async data => await OnGestureEnded(data));
- SetTapCallback(GestureType.LongTap, GestureState.Abort, async data => await OnGestureAborted(data));
+ SetTapCallback(GestureType.LongTap, GestureState.Start, OnLongTapStarted);
+ SetTapCallback(GestureType.LongTap, GestureState.End, OnGestureEnded);
+ SetTapCallback(GestureType.LongTap, GestureState.Abort, OnGestureAborted);
}
public TouchTapGestureRecognizer(EvasObject parent, TouchEffect effect)
@@ -69,16 +68,16 @@ public TouchTapGestureRecognizer(EvasObject parent, TouchEffect effect)
public bool IsCanceled { get; set; } = true;
- async Task OnTapStarted(TapData data)
+ void OnTapStarted(TapData data)
{
if (effect?.IsDisabled ?? true)
return;
IsCanceled = false;
- await HandleTouch(TouchStatus.Started, TouchInteractionStatus.Started);
+ HandleTouch(TouchStatus.Started, TouchInteractionStatus.Started);
}
- async Task OnLongTapStarted(TapData data)
+ void OnLongTapStarted(TapData data)
{
if (effect?.IsDisabled ?? true)
return;
@@ -86,20 +85,20 @@ async Task OnLongTapStarted(TapData data)
IsCanceled = false;
longTapStarted = true;
- await HandleTouch(TouchStatus.Started, TouchInteractionStatus.Started);
+ HandleTouch(TouchStatus.Started, TouchInteractionStatus.Started);
}
- async Task OnGestureEnded(TapData data)
+ void OnGestureEnded(TapData data)
{
if (effect == null || effect.IsDisabled)
return;
- await HandleTouch(effect.Status == TouchStatus.Started ? TouchStatus.Completed : TouchStatus.Canceled, TouchInteractionStatus.Completed);
+ HandleTouch(effect.Status == TouchStatus.Started ? TouchStatus.Completed : TouchStatus.Canceled, TouchInteractionStatus.Completed);
IsCanceled = true;
tapCompleted = true;
}
- async Task OnGestureAborted(TapData data)
+ void OnGestureAborted(TapData data)
{
if (effect?.IsDisabled ?? true)
return;
@@ -111,11 +110,11 @@ async Task OnGestureAborted(TapData data)
return;
}
- await HandleTouch(TouchStatus.Canceled, TouchInteractionStatus.Completed);
+ HandleTouch(TouchStatus.Canceled, TouchInteractionStatus.Completed);
IsCanceled = true;
}
- public async Task HandleTouch(TouchStatus status, TouchInteractionStatus? touchInteractionStatus = null)
+ public void HandleTouch(TouchStatus status, TouchInteractionStatus? touchInteractionStatus = null)
{
if (IsCanceled || effect == null)
return;
@@ -129,7 +128,7 @@ public async Task HandleTouch(TouchStatus status, TouchInteractionStatus? touchI
touchInteractionStatus = null;
}
- await effect.HandleTouch(status);
+ effect.HandleTouch(status);
if (touchInteractionStatus.HasValue)
effect.HandleUserInteraction(touchInteractionStatus.Value);
@@ -140,7 +139,7 @@ public async Task HandleTouch(TouchStatus status, TouchInteractionStatus? touchI
return;
var control = effect.Element;
- if (!(Platform.GetOrCreateRenderer(control)?.NativeView is Widget nativeView))
+ if (Platform.GetOrCreateRenderer(control)?.NativeView is not Widget nativeView)
return;
if (status == TouchStatus.Started)
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/TouchEffect.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/TouchEffect.shared.cs
index 4be18f20f..b75220400 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/TouchEffect.shared.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Touch/TouchEffect.shared.cs
@@ -910,19 +910,15 @@ static void TryGenerateEffect(BindableObject? bindable, object oldValue, object
view.Effects.Add(new TouchEffect { IsAutoGenerated = true });
}
- static async void ForceUpdateStateAndTryGenerateEffect(BindableObject bindable, object oldValue, object newValue)
+ static void ForceUpdateStateAndTryGenerateEffect(BindableObject bindable, object oldValue, object newValue)
{
- if (GetFrom(bindable)?.ForceUpdateState() is Task forceUpdateState)
- await forceUpdateState;
-
+ GetFrom(bindable)?.ForceUpdateState();
TryGenerateEffect(bindable, oldValue, newValue);
}
- static async void ForceUpdateStateWithoutAnimationAndTryGenerateEffect(BindableObject bindable, object oldValue, object newValue)
+ static void ForceUpdateStateWithoutAnimationAndTryGenerateEffect(BindableObject bindable, object oldValue, object newValue)
{
- if (GetFrom(bindable)?.ForceUpdateState() is Task forceUpdateState)
- await forceUpdateState;
-
+ GetFrom(bindable)?.ForceUpdateState();
TryGenerateEffect(bindable, oldValue, newValue);
}
@@ -1109,8 +1105,6 @@ public bool? IsToggled
ForceUpdateState();
}
-
- async void ForceUpdateState() => await this.ForceUpdateState(false);
}
}
@@ -1128,18 +1122,18 @@ public bool? IsToggled
?? effects?.FirstOrDefault();
}
- internal Task HandleTouch(TouchStatus status)
+ internal void HandleTouch(TouchStatus status)
=> gestureManager.HandleTouch(this, status);
internal void HandleUserInteraction(TouchInteractionStatus interactionStatus)
=> gestureManager.HandleUserInteraction(this, interactionStatus);
- internal ValueTask HandleHover(HoverStatus status)
+ internal void HandleHover(HoverStatus status)
=> gestureManager.HandleHover(this, status);
- internal async Task RaiseStateChanged()
+ internal void RaiseStateChanged()
{
- await ForceUpdateState();
+ ForceUpdateState();
HandleLongPress();
weakEventManager.RaiseEvent(Element, new TouchStateChangedEventArgs(State), nameof(StateChanged));
}
@@ -1150,9 +1144,9 @@ internal void RaiseInteractionStatusChanged()
internal void RaiseStatusChanged()
=> weakEventManager.RaiseEvent(Element, new TouchStatusChangedEventArgs(Status), nameof(StatusChanged));
- internal async Task RaiseHoverStateChanged()
+ internal void RaiseHoverStateChanged()
{
- await ForceUpdateState();
+ ForceUpdateState();
weakEventManager.RaiseEvent(Element, new HoverStateChangedEventArgs(HoverState), nameof(HoverStateChanged));
}
@@ -1162,12 +1156,12 @@ internal void RaiseHoverStatusChanged()
internal void RaiseCompleted()
=> weakEventManager.RaiseEvent(Element, new TouchCompletedEventArgs(CommandParameter), nameof(Completed));
- internal async Task ForceUpdateState(bool animated = true)
+ internal void ForceUpdateState(bool animated = true)
{
if (Element == null)
return;
- await gestureManager.ChangeStateAsync(this, animated);
+ gestureManager.ChangeStateAsync(this, animated).SafeFireAndForget();
}
internal void HandleLongPress()
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Helpers/SafeFireAndForgetExtensions.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Helpers/SafeFireAndForgetExtensions.shared.cs
new file mode 100644
index 000000000..b2abbe0f6
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Helpers/SafeFireAndForgetExtensions.shared.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Threading.Tasks;
+
+// Inspired by https://github.com/brminnick/AsyncAwaitBestPractices
+namespace Xamarin.CommunityToolkit.Helpers
+{
+ ///
+ /// Extension methods for System.Threading.Tasks.Task and System.Threading.Tasks.ValueTask
+ ///
+ static class SafeFireAndForgetExtensions
+ {
+ ///
+ /// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.
+ ///
+ /// ValueTask.
+ /// If an exception is thrown in the ValueTask, onException will execute. If onException is null, the exception will be re-thrown
+ /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread
+ public static void SafeFireAndForget(this ValueTask task, in Action? onException = null, in bool continueOnCapturedContext = false) => HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
+
+ ///
+ /// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.
+ ///
+ /// ValueTask.
+ /// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown
+ /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread
+ /// Exception type. If an exception is thrown of a different type, it will not be handled
+ public static void SafeFireAndForget(this ValueTask task, in Action? onException = null, in bool continueOnCapturedContext = false) where TException : Exception => HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
+
+ ///
+ /// Safely execute the Task without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.
+ ///
+ /// Task.
+ /// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown
+ /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread
+ public static void SafeFireAndForget(this Task task, in Action? onException = null, in bool continueOnCapturedContext = false) => HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
+
+ ///
+ /// Safely execute the Task without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.
+ ///
+ /// Task.
+ /// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown
+ /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread
+ /// Exception type. If an exception is thrown of a different type, it will not be handled
+ public static void SafeFireAndForget(this Task task, in Action? onException = null, in bool continueOnCapturedContext = false) where TException : Exception => HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
+
+ static async void HandleSafeFireAndForget(ValueTask valueTask, bool continueOnCapturedContext, Action? onException) where TException : Exception
+ {
+ try
+ {
+ await valueTask.ConfigureAwait(continueOnCapturedContext);
+ }
+ catch (TException ex) when (onException != null)
+ {
+ onException(ex);
+ }
+ }
+
+ static async void HandleSafeFireAndForget(Task task, bool continueOnCapturedContext, Action? onException) where TException : Exception
+ {
+ try
+ {
+ await task.ConfigureAwait(continueOnCapturedContext);
+ }
+ catch (TException ex) when (onException != null)
+ {
+ onException(ex);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/ObjectModel/Internals/BaseAsyncCommand.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/ObjectModel/Internals/BaseAsyncCommand.shared.cs
index 8f67a4e10..1336600d3 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/ObjectModel/Internals/BaseAsyncCommand.shared.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/ObjectModel/Internals/BaseAsyncCommand.shared.cs
@@ -4,6 +4,7 @@
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.CommunityToolkit.Exceptions;
+using Xamarin.CommunityToolkit.Helpers;
namespace Xamarin.CommunityToolkit.ObjectModel.Internals
{
@@ -100,11 +101,11 @@ void ICommand.Execute(object parameter)
switch (parameter)
{
case TExecute validParameter:
- Execute(validParameter);
+ ExecuteAsync(validParameter).SafeFireAndForget(onException, continueOnCapturedContext);
break;
case null when !typeof(TExecute).GetTypeInfo().IsValueType:
- Execute((TExecute?)parameter);
+ ExecuteAsync((TExecute?)parameter).SafeFireAndForget(onException, continueOnCapturedContext);
break;
case null:
@@ -113,9 +114,6 @@ void ICommand.Execute(object parameter)
default:
throw new InvalidCommandParameterException(typeof(TExecute), parameter.GetType());
}
-
- // Use local method to defer async void from ICommand.Execute, allowing InvalidCommandParameterException to be thrown on the calling thread context before reaching an async method
- async void Execute(TExecute? parameter) => await ExecuteAsync(parameter).ConfigureAwait(continueOnCapturedContext);
}
}
}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/ObjectModel/Internals/BaseAsyncValueCommand.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/ObjectModel/Internals/BaseAsyncValueCommand.shared.cs
index eef0a1cff..2f6edbc9a 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/ObjectModel/Internals/BaseAsyncValueCommand.shared.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/ObjectModel/Internals/BaseAsyncValueCommand.shared.cs
@@ -4,6 +4,7 @@
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.CommunityToolkit.Exceptions;
+using Xamarin.CommunityToolkit.Helpers;
namespace Xamarin.CommunityToolkit.ObjectModel.Internals
{
@@ -100,11 +101,11 @@ void ICommand.Execute(object parameter)
switch (parameter)
{
case TExecute validParameter:
- Execute(validParameter);
+ ExecuteAsync(validParameter).SafeFireAndForget(onException, continueOnCapturedContext);
break;
case null when !typeof(TExecute).GetTypeInfo().IsValueType:
- Execute((TExecute?)parameter);
+ ExecuteAsync((TExecute?)parameter).SafeFireAndForget(onException, continueOnCapturedContext);
break;
case null:
@@ -113,9 +114,6 @@ void ICommand.Execute(object parameter)
default:
throw new InvalidCommandParameterException(typeof(TExecute), parameter.GetType());
}
-
- // Use local method to defer async void from ICommand.Execute, allowing InvalidCommandParameterException to be thrown on the calling thread context before reaching an async method
- async void Execute(TExecute? parameter) => await ExecuteAsync(parameter).ConfigureAwait(continueOnCapturedContext);
}
}
}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/MediaElement/Android/FormsVideoView.android.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/MediaElement/Android/FormsVideoView.android.cs
index 45faabe78..8c2c1711c 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/MediaElement/Android/FormsVideoView.android.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/MediaElement/Android/FormsVideoView.android.cs
@@ -53,12 +53,12 @@ protected void ExtractMetadata(MediaMetadataRetriever retriever)
public override async void SetVideoURI(global::Android.Net.Uri? uri, IDictionary? headers)
{
if (uri != null)
- await GetMetadata(uri, headers);
+ await SetMetadata(uri, headers);
base.SetVideoURI(uri, headers);
}
- protected async Task GetMetadata(global::Android.Net.Uri uri, IDictionary? headers)
+ protected async Task SetMetadata(global::Android.Net.Uri uri, IDictionary? headers)
{
var retriever = new MediaMetadataRetriever();
From f3eb1ae6c2815a0a0f86e157ec629119ef97b588 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Wed, 10 Mar 2021 19:44:19 -0800
Subject: [PATCH 2/2] Change `public` to `internal`
---
.../Helpers/SafeFireAndForgetExtensions.shared.cs | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Helpers/SafeFireAndForgetExtensions.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Helpers/SafeFireAndForgetExtensions.shared.cs
index b2abbe0f6..2ad792125 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Helpers/SafeFireAndForgetExtensions.shared.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Helpers/SafeFireAndForgetExtensions.shared.cs
@@ -15,7 +15,8 @@ static class SafeFireAndForgetExtensions
/// ValueTask.
/// If an exception is thrown in the ValueTask, onException will execute. If onException is null, the exception will be re-thrown
/// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread
- public static void SafeFireAndForget(this ValueTask task, in Action? onException = null, in bool continueOnCapturedContext = false) => HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
+ internal static void SafeFireAndForget(this ValueTask task, in Action? onException = null, in bool continueOnCapturedContext = false) =>
+ HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
///
/// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.
@@ -24,7 +25,8 @@ static class SafeFireAndForgetExtensions
/// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown
/// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread
/// Exception type. If an exception is thrown of a different type, it will not be handled
- public static void SafeFireAndForget(this ValueTask task, in Action? onException = null, in bool continueOnCapturedContext = false) where TException : Exception => HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
+ internal static void SafeFireAndForget(this ValueTask task, in Action? onException = null, in bool continueOnCapturedContext = false) where TException : Exception =>
+ HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
///
/// Safely execute the Task without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.
@@ -32,7 +34,8 @@ static class SafeFireAndForgetExtensions
/// Task.
/// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown
/// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread
- public static void SafeFireAndForget(this Task task, in Action? onException = null, in bool continueOnCapturedContext = false) => HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
+ internal static void SafeFireAndForget(this Task task, in Action? onException = null, in bool continueOnCapturedContext = false) =>
+ HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
///
/// Safely execute the Task without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.
@@ -41,7 +44,8 @@ static class SafeFireAndForgetExtensions
/// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown
/// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread
/// Exception type. If an exception is thrown of a different type, it will not be handled
- public static void SafeFireAndForget(this Task task, in Action? onException = null, in bool continueOnCapturedContext = false) where TException : Exception => HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
+ internal static void SafeFireAndForget(this Task task, in Action? onException = null, in bool continueOnCapturedContext = false) where TException : Exception =>
+ HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
static async void HandleSafeFireAndForget(ValueTask valueTask, bool continueOnCapturedContext, Action? onException) where TException : Exception
{