Skip to content

fix: skip full viewer reset on progress-only notifications#617

Open
i4innovationnet wants to merge 1 commit intoespresso3389:masterfrom
i4innovationnet:fix/skip-reset-on-progress-notification
Open

fix: skip full viewer reset on progress-only notifications#617
i4innovationnet wants to merge 1 commit intoespresso3389:masterfrom
i4innovationnet:fix/skip-reset-on-progress-notification

Conversation

@i4innovationnet
Copy link
Copy Markdown

@i4innovationnet i4innovationnet commented Mar 9, 2026

Problem

When using preferRangeAccess: true on large PDFs (≥10 MB), the viewer repeatedly flashes white and fails to render pages stably. Pages load, go white, re-load in an infinite loop until all byte ranges are cached.

Root cause

PdfDocumentListenable._progress() (pdf_document_ref.dart:513-516) calls notifyListeners() on every downloaded HTTP chunk:

void _progress(int progress, [int? total]) {
  _bytesDownloaded = progress;
  _totalBytes = total;
  notifyListeners(); // fires on every chunk
}

The viewer's _onDocumentChanged() (pdf_viewer.dart:324) is registered as a listener and unconditionally releases all cached page images, clears layout, and sets _initialized = false — before checking if the document reference actually changed:

void _onDocumentChanged() async {
  _layout = null;                          // ← unconditional
  // ...
  _imageCache.releaseAllImages();          // ← destroys all rendered pages
  _magnifierImageCache.releaseAllImages();
  // ...
  _initialized = false;                   // ← prevents painting
  // ...
  final document = listenable.document;    // ← check happens AFTER reset

After loadDocument() returns, PDFium continues reading blocks on-demand for rendering visible pages (via the captured read callback in PdfFileCache). These reads trigger _progress()notifyListeners() → full reset, creating an infinite loop where rendering triggers downloads which destroy the renders.

Reproduction

  1. Host a PDF ≥ 10 MB behind an HTTP server that supports Range requests (e.g., Firebase Storage)
  2. Use PdfViewer.uri(uri, preferRangeAccess: true) or PdfViewer(PdfDocumentRefUri(uri, preferRangeAccess: true))
  3. Observe pages flash white repeatedly during and after loading
  4. Pages go through load → white → re-init → load cycles

Test results (iPad Pro, 22.3 MB / 18-page PDF over Firebase Storage)

Without this fix (stock pdfrx 2.2.24, no workaround):

  • 4,525 total notifyListeners() calls, 2,858 post-load
  • Each post-load notification triggered a full nuclear reset (releaseAllImages, _initialized = false)
  • onViewerReady fired 7+ times — the viewer kept getting destroyed and re-initialized in a loop
  • Visually: pages loaded → went white → looped → eventually re-rendered once all byte ranges were cached

With this fix (same PDF, same device, same network):

  • 4,471 total notifyListeners() calls, 2,858 post-load (same notification volume)
  • Zero resets — all 2,858 post-load notifications hit the early-return guard
  • onViewerReady fired once
  • Visually: pages rendered and stayed stable. Scrolled all 18 pages, waited 30+ seconds, re-opened — no white flashing at any point

Related issues

Fix

Add a 3-line early return at the top of _onDocumentChanged() that skips the reset when the document reference is the same object:

final currentDoc = widget.documentRef.resolveListenable().document;
if (currentDoc != null && currentDoc == _document) return;

Edge cases (all verified safe)

Scenario currentDoc _document Guard fires? Correct?
Progress notification after load (same doc) loaded doc same doc Yes → skip
Initial load (null → loaded) loaded doc null No → full reset
Pre-load progress (both null) null null No → full reset ✅ (harmless)
Document swap (A → B) doc B doc A No → full reset
Error (loaded → null) null loaded doc No → full reset
didUpdateWidget same doc loaded doc same doc Yes → skip

Impact

  • Fixes white-page flashing with preferRangeAccess: true on large PDFs
  • No behavioral change for non-range-access usage (progress notifications only fire during HTTP downloads)
  • Minimal, backwards-compatible change (3 lines of code)
  • Tested on physical iPad with 22.3 MB PDF — verified both bug reproduction and fix

PdfDocumentListenable._progress() calls notifyListeners() on every
downloaded HTTP chunk during range-access loading. The viewer's
_onDocumentChanged listener unconditionally releases all cached page
images (releaseAllImages), clears layout, and sets _initialized=false
on EVERY notification — even when the document reference hasn't changed.

With preferRangeAccess enabled on a large PDF, hundreds of chunks arrive
during and after loading. Each triggers a full nuclear reset, causing
rendered pages to flash white repeatedly and preventing stable rendering.

Fix: add an early return at the top of _onDocumentChanged() that skips
the reset when the document reference is the same object. This correctly
distinguishes progress-only notifications from actual document changes
(initial load, document swap, error/null).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant