diff --git a/src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs b/src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs index a484ebe1f696..b58075ff0664 100644 --- a/src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs +++ b/src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs @@ -95,6 +95,10 @@ public static AColor ColorAtPoint(this Bitmap bitmap, int x, int y, bool include return true; }); + // Android doesn't handle adding and removing views in parallel very well + // If a view is removed while a different test triggers a layout then you hit + // a NRE exception + static SemaphoreSlim _attachAndRunSemaphore = new SemaphoreSlim(1); public static async Task AttachAndRun(this AView view, Func> action) { if (view.Parent is WrapperView wrapper) @@ -115,17 +119,21 @@ public static async Task AttachAndRun(this AView view, Func> actio var act = context.GetActivity()!; var rootView = act.FindViewById(Android.Resource.Id.Content)!; - layout.AddView(view); - rootView.AddView(layout); + view.Id = AView.GenerateViewId(); + layout.Id = AView.GenerateViewId(); try { + await _attachAndRunSemaphore.WaitAsync(); + layout.AddView(view); + rootView.AddView(layout); return await Run(view, action); } finally { rootView.RemoveView(layout); layout.RemoveView(view); + _attachAndRunSemaphore.Release(); } } else