Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ void IAppearanceObserver.OnAppearanceChanged(ShellAppearance appearance)
AToolbar _toolbar;
IShellToolbarTracker _toolbarTracker;
bool _disposed;
bool _destroyed;

public ShellContentFragment(IShellContext shellContext, ShellContent shellContent)
{
Expand Down Expand Up @@ -167,13 +168,18 @@ public override AView OnCreateView(LayoutInflater inflater, ViewGroup container,

void Destroy()
{
if (_destroyed)
return;

_destroyed = true;

// If the user taps very quickly on back button multiple times to pop a page,
// the app enters background state in the middle of the animation causing the fragment to be destroyed without completing the animation.
// That'll cause `IAnimationListener.onAnimationEnd` to not be called, so we need to call it manually if something is still subscribed to the event
// to avoid the navigation `TaskCompletionSource` to be stuck forever.
AnimationFinished?.Invoke(this, EventArgs.Empty);

((IShellController)_shellContext.Shell).RemoveAppearanceObserver(this);
(_shellContext?.Shell as IShellController)?.RemoveAppearanceObserver(this);

if (_shellContent != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,61 @@ await CreateHandlerAndAddToWindow<ShellRenderer>(shell, (handler) =>
});
}

[Fact(DisplayName = "ShellContentFragment.Destroy handles null _shellContext gracefully")]
public async Task ShellContentFragmentDestroyHandlesNullShellContext()
{
SetupBuilder();

var shell = await CreateShellAsync(shell =>
{
shell.Items.Add(new TabBar()
{
Items =
{
new ShellContent()
{
Route = "Item1",
Content = new ContentPage { Title = "Page 1" }
},
new ShellContent()
{
Route = "Item2",
Content = new ContentPage { Title = "Page 2" }
},
}
});
});

await CreateHandlerAndAddToWindow<ShellHandler>(shell, async (handler) =>
{
await OnLoadedAsync(shell.CurrentPage);
await OnNavigatedToAsync(shell.CurrentPage);

// Navigate to trigger fragment creation
await shell.GoToAsync("//Item2");
await OnNavigatedToAsync(shell.CurrentPage);

// Test normal destruction - should work without issues
await shell.GoToAsync("//Item1");
await OnNavigatedToAsync(shell.CurrentPage);

// Test null context scenario
var exception = Record.Exception(() =>
{
// Create fragment with null context - this should not throw
Page page = new ContentPage();
var fragment = new ShellContentFragment((IShellContext)null, page);

// Dispose the fragment which calls Destroy internally
// This validates the null-conditional operators in Destroy method
fragment.Dispose();
});

// Verify no exception was thrown - validates (_shellContext?.Shell as IShellController)?.RemoveAppearanceObserver(this);
Assert.Null(exception);
});
}

protected AView GetFlyoutPlatformView(ShellRenderer shellRenderer)
{
var drawerLayout = GetDrawerLayout(shellRenderer);
Expand Down
Loading