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

  1. Dispose the TopLevel/TopLevelImpl fully and unsubscribe all render timers before the view is destroyed so no late Lock() call occurs.
  2. If you maintain a custom render loop, null-check InternalView before requesting a frame and bail out silently when it is null.
  3. Ensure the host Activity/Fragment lifecycle tears down the Avalonia view in onStop/onDestroy before any background task can trigger rendering.
  4. 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

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


AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13). Data as JSON: /api/errors/182ca8089692a486. Report an issue: GitHub.