Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

Commit 5b6b4f0

Browse files
authored
Fix NullReferenceException when displaying Toast or SnackBar (#963)
1 parent c138f20 commit 5b6b4f0

File tree

8 files changed

+56
-27
lines changed

8 files changed

+56
-27
lines changed

src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/PageExtension.shared.cs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Xamarin.CommunityToolkit.Extensions
1010
{
1111
public static class PageExtension
1212
{
13-
public static Task DisplayToastAsync(this Page page, string message, int durationMilliseconds = 3000)
13+
public static async Task DisplayToastAsync(this Page page, string message, int durationMilliseconds = 3000)
1414
{
1515
_ = page ?? throw new ArgumentNullException(nameof(page));
1616

@@ -26,11 +26,11 @@ public static Task DisplayToastAsync(this Page page, string message, int duratio
2626
#endif
2727
};
2828
var snackBar = new SnackBar();
29-
snackBar.Show(page, args);
30-
return args.Result.Task;
29+
await snackBar.Show(page, args);
30+
await args.Result.Task;
3131
}
3232

33-
public static Task DisplayToastAsync(this Page page, ToastOptions toastOptions)
33+
public static async Task DisplayToastAsync(this Page page, ToastOptions toastOptions)
3434
{
3535
_ = page ?? throw new ArgumentNullException(nameof(page));
3636

@@ -43,11 +43,11 @@ public static Task DisplayToastAsync(this Page page, ToastOptions toastOptions)
4343
BackgroundColor = arguments.BackgroundColor,
4444
IsRtl = arguments.IsRtl
4545
};
46-
snackBar.Show(page, options);
47-
return options.Result.Task;
46+
await snackBar.Show(page, options);
47+
await options.Result.Task;
4848
}
4949

50-
public static Task<bool> DisplaySnackBarAsync(this Page page, string message, string actionButtonText, Func<Task> action, int durationMilliseconds = 3000)
50+
public static async Task<bool> DisplaySnackBarAsync(this Page page, string message, string actionButtonText, Func<Task> action, int durationMilliseconds = 3000)
5151
{
5252
_ = page ?? throw new ArgumentNullException(nameof(page));
5353

@@ -71,18 +71,22 @@ public static Task<bool> DisplaySnackBarAsync(this Page page, string message, st
7171
#endif
7272
};
7373
var snackBar = new SnackBar();
74-
snackBar.Show(page, options);
75-
return options.Result.Task;
74+
await snackBar.Show(page, options);
75+
var isButtonClicked = await options.Result.Task;
76+
77+
return isButtonClicked;
7678
}
7779

78-
public static Task<bool> DisplaySnackBarAsync(this Page page, SnackBarOptions snackBarOptions)
80+
public static async Task<bool> DisplaySnackBarAsync(this Page page, SnackBarOptions snackBarOptions)
7981
{
8082
_ = page ?? throw new ArgumentNullException(nameof(page));
8183

8284
var snackBar = new SnackBar();
83-
var arguments = snackBarOptions ?? new SnackBarOptions();
84-
snackBar.Show(page, arguments);
85-
return arguments.Result.Task;
85+
var options = snackBarOptions ?? new SnackBarOptions();
86+
await snackBar.Show(page, options);
87+
var isButtonClicked = await options.Result.Task;
88+
89+
return isButtonClicked;
8690
}
8791
}
8892
}

src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/SnackBar.android.cs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
using Xamarin.Forms;
1+
using System;
2+
using System.Threading.Tasks;
3+
using Xamarin.Forms;
24
using Android.Graphics;
35
using Android.Widget;
46
using Xamarin.Forms.Platform.Android;
57
using Xamarin.CommunityToolkit.UI.Views.Options;
68
using Android.Util;
7-
using System;
89
#if MONOANDROID10_0
910
using AndroidSnackBar = Google.Android.Material.Snackbar.Snackbar;
1011
#else
@@ -15,10 +16,10 @@ namespace Xamarin.CommunityToolkit.UI.Views
1516
{
1617
class SnackBar
1718
{
18-
internal void Show(Page sender, SnackBarOptions arguments)
19+
internal async ValueTask Show(Page sender, SnackBarOptions arguments)
1920
{
20-
var view = Platform.GetRenderer(sender).View;
21-
var snackBar = AndroidSnackBar.Make(view, arguments.MessageOptions.Message, (int)arguments.Duration.TotalMilliseconds);
21+
var renderer = await GetRendererWithRetries(sender) ?? throw new ArgumentException("Provided page cannot be parent to SnackBar", nameof(sender));
22+
var snackBar = AndroidSnackBar.Make(renderer.View, arguments.MessageOptions.Message, (int)arguments.Duration.TotalMilliseconds);
2223
var snackBarView = snackBar.View;
2324
if (arguments.BackgroundColor != Forms.Color.Default)
2425
{
@@ -100,6 +101,20 @@ internal void Show(Page sender, SnackBarOptions arguments)
100101
snackBar.Show();
101102
}
102103

104+
/// <summary>
105+
/// Tries to get renderer multiple times since it can be null while switching tabs in Shell.
106+
/// See this bug for more info: https://github.com/xamarin/Xamarin.Forms/issues/13950
107+
/// </summary>
108+
static async Task<IVisualElementRenderer?> GetRendererWithRetries(Page page, int retryCount = 5)
109+
{
110+
var renderer = Platform.GetRenderer(page);
111+
if (renderer != null || retryCount <= 0)
112+
return renderer;
113+
114+
await Task.Delay(50);
115+
return await GetRendererWithRetries(page, retryCount - 1);
116+
}
117+
103118
class SnackBarCallback : AndroidSnackBar.BaseCallback
104119
{
105120
readonly SnackBarOptions arguments;

src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/SnackBar.gtk.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Linq;
2+
using System.Threading.Tasks;
23
using System.Timers;
34
using Gtk;
45
using Pango;
@@ -13,7 +14,7 @@ class SnackBar
1314
{
1415
Timer? snackBarTimer;
1516

16-
public void Show(Page page, SnackBarOptions arguments)
17+
public ValueTask Show(Page page, SnackBarOptions arguments)
1718
{
1819
var mainWindow = (Platform.GetRenderer(page).Container.Child as Forms.Platform.GTK.Controls.Page)?.Children[0] as VBox;
1920
var snackBarLayout = GetSnackBarLayout(mainWindow, arguments);
@@ -29,6 +30,7 @@ public void Show(Page page, SnackBarOptions arguments)
2930
};
3031

3132
snackBarTimer.Start();
33+
return default;
3234
}
3335

3436
HBox GetSnackBarLayout(Container? container, SnackBarOptions arguments)

src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/SnackBar.ios.macos.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace Xamarin.CommunityToolkit.UI.Views
1717
{
1818
class SnackBar
1919
{
20-
internal void Show(Page sender, SnackBarOptions arguments)
20+
internal ValueTask Show(Page sender, SnackBarOptions arguments)
2121
{
2222
var snackBar = NativeSnackBar.MakeSnackBar(arguments.MessageOptions.Message)
2323
.SetDuration(arguments.Duration.TotalMilliseconds)
@@ -124,6 +124,8 @@ internal void Show(Page sender, SnackBarOptions arguments)
124124
}
125125

126126
snackBar.Show();
127+
128+
return default;
127129
}
128130
}
129131
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
using System;
2+
using System.Threading.Tasks;
23
using Xamarin.CommunityToolkit.UI.Views.Options;
34
using Xamarin.Forms;
45

56
namespace Xamarin.CommunityToolkit.UI.Views
67
{
78
class SnackBar
89
{
9-
internal void Show(Page sender, SnackBarOptions arguments) => throw new PlatformNotSupportedException();
10+
internal ValueTask Show(Page sender, SnackBarOptions arguments) => throw new PlatformNotSupportedException();
1011
}
1112
}

src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/SnackBar.tizen.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
using System;
22
using System.Linq;
3+
using System.Threading.Tasks;
34
using Xamarin.CommunityToolkit.UI.Views.Options;
45
using EButton = ElmSharp.Button;
56

67
namespace Xamarin.CommunityToolkit.UI.Views
78
{
89
class SnackBar
910
{
10-
internal void Show(Forms.Page sender, SnackBarOptions arguments)
11+
internal ValueTask Show(Forms.Page sender, SnackBarOptions arguments)
1112
{
1213
var snackBarDialog =
1314
Forms.Platform.Tizen.Native.Dialog.CreateDialog(Forms.Forms.NativeParent,
@@ -36,11 +37,11 @@ internal void Show(Forms.Page sender, SnackBarOptions arguments)
3637
}
3738

3839
snackBarDialog.TimedOut += (s, evt) => DismissSnackBar();
39-
4040
snackBarDialog.BackButtonPressed += (s, evt) => DismissSnackBar();
41-
4241
snackBarDialog.Show();
4342

43+
return default;
44+
4445
void DismissSnackBar()
4546
{
4647
snackBarDialog.Dismiss();

src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/SnackBar.uwp.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Threading.Tasks;
23
using Windows.UI.Xaml;
34
using Windows.UI.Xaml.Controls;
45
using Windows.UI.Xaml.Media;
@@ -34,7 +35,7 @@ class SnackBar
3435
return null;
3536
}
3637

37-
internal void Show(Forms.Page page, SnackBarOptions arguments)
38+
internal ValueTask Show(Forms.Page page, SnackBarOptions arguments)
3839
{
3940
var snackBarLayout = new SnackBarLayout(arguments);
4041
var pageControl = Platform.GetRenderer(page).ContainerElement.Parent;
@@ -59,6 +60,7 @@ internal void Show(Forms.Page page, SnackBarOptions arguments)
5960
grid.RowDefinitions.Add(snackBarRow);
6061
grid.Children.Add(snackBarLayout);
6162
Grid.SetRow(snackBarLayout, grid.RowDefinitions.Count - 1);
63+
return default;
6264
}
6365
}
6466
}

src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/SnackBar.wpf.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Windows.Forms;
1+
using System.Threading.Tasks;
2+
using System.Windows.Forms;
23
using Xamarin.CommunityToolkit.UI.Views.Helpers;
34
using Xamarin.CommunityToolkit.UI.Views.Options;
45
using Xamarin.Forms;
@@ -11,7 +12,7 @@ class SnackBar
1112
{
1213
Timer? snackBarTimer;
1314

14-
internal void Show(Page page, SnackBarOptions arguments)
15+
internal ValueTask Show(Page page, SnackBarOptions arguments)
1516
{
1617
var formsAppBar = System.Windows.Application.Current.MainWindow.FindChild<FormsAppBar>("PART_BottomAppBar");
1718
var currentContent = formsAppBar.Content;
@@ -31,6 +32,7 @@ internal void Show(Page page, SnackBarOptions arguments)
3132
};
3233
snackBarTimer.Start();
3334
formsAppBar.Content = snackBar;
35+
return default;
3436
}
3537
}
3638
}

0 commit comments

Comments
 (0)