AvaloniaUI/Avalonia · error · InvalidOperationException
TopLevel.InternalView was not expected to be null.
Error message
TopLevel.InternalView was not expected to be null.
What it means
Thrown by FramebufferManager.Lock() when _topLevel.InternalView (the InvalidationAwareSurfaceView) is null. The framebuffer path requires a live surface view; a null means the TopLevelImpl has been disposed or its SurfaceViewImpl was never/ no longer instantiated when a frame was requested.
Source
Thrown at src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs:17
using System;
using Avalonia.Platform.Surfaces;
using Avalonia.Platform;
namespace Avalonia.Android.Platform.SkiaPlatform
{
internal sealed class FramebufferManager : IFramebufferPlatformSurface
{
private readonly TopLevelImpl _topLevel;
public FramebufferManager(TopLevelImpl topLevel)
{
_topLevel = topLevel;
}
public ILockedFramebuffer Lock() => new AndroidFramebuffer(
_topLevel.InternalView ?? throw new InvalidOperationException("TopLevel.InternalView was not expected to be null."),
_topLevel.RenderScaling);
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);
}
}
View on GitHub (pinned to 11c5427268)
Solutions
- Dispose the TopLevel/TopLevelImpl fully and unsubscribe all render timers before the view is destroyed so no late Lock() call occurs.
- If you maintain a custom render loop, null-check InternalView before requesting a frame and bail out silently when it is null.
- Ensure the host Activity/Fragment lifecycle tears down the Avalonia view in onStop/onDestroy before any background task can trigger rendering.
- Report upstream if this fires under normal lifecycle — it indicates a missing render-scheduler detach on disposal.
Example fix
// before
// render callback fires unconditionally
compositor.RequestCompositionPaint();
// after — guard the render request against a torn-down view
if (_topLevelImpl.InternalView is not null)
compositor.RequestCompositionPaint(); Defensive patterns
Strategy: validation
Validate before calling
// before requesting a frame, ensure the TopLevel still has its view if (_topLevelImpl.InternalView is null) return;
Type guard
static bool IsTopLevelAlive(TopLevelImpl? impl) => impl?.InternalView is not null;
Try / catch
// prefer preventing the call; if you cannot, isolate the render tick:
try { compositor.RequestPaint(); }
catch (InvalidOperationException) when (topLevel.InternalView is null) { /* shutting down */ } Prevention
- Fully dispose TopLevel/TopLevelImpl and stop render timers before destroying the host view.
- Null-check InternalView in custom render loops before each frame.
- Tear down the Avalonia view in the host Activity/Fragment lifecycle (onStop/onDestroy).
- Avoid scheduling renders from background tasks that outlive the view.
When it happens
Trigger: Calling FramebufferManager.Lock() after TopLevelImpl has disposed its _view, or during teardown of an embedded AvaloniaView while a pending render callback still fires. Occurs when the compositor/scheduler still holds a reference to the framebuffer surface after the underlying view was torn down.
Common situations: Disposing an AvaloniaView (Activity finish, fragment destruction) while a render timer or animation is still ticking; hot-reload scenarios; rapid Activity recreation in config changes where the old TopLevelImpl receives a late render request.
Related errors
- Unable to obtain ANativeWindow
- Surface handle can't be 0x0
- AndroidPlatformOptions.RenderingMode must not be empty or nu
- AndroidPlatformOptions.RenderingMode has a value of "{opts.R
- The control isn't currently attached to a toplevel
AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13).
Data as JSON: /api/errors/182ca8089692a486.
Report an issue: GitHub.