Skip to content

Dissolve the loader chain - #583

Merged
andiwand merged 1 commit into
mainfrom
dissolve-the-loader-chain
Aug 10, 2026
Merged

Dissolve the loader chain#583
andiwand merged 1 commit into
mainfrom
dissolve-the-loader-chain

Conversation

@andiwand

@andiwand andiwand commented Aug 10, 2026

Copy link
Copy Markdown
Member

With one loader left there is no chain to drive. The machinery built for four goes, and the work it was carrying moves to components named after it.

MainActivity
  └─ DocumentLoader : AndroidViewModel     ← LoaderService + FileLoader + LoaderServiceQueue
       ├─ FileCache        store the working copy   ← AndroidFileCache + half of MetadataLoader
       ├─ FileIdentifier   name it, type it         ← the other half
       ├─ CoreLoader       render, host, edit       ← no longer a FileLoader
       └─ DocumentSaver    back up, write, roll back ← was inline in LoaderService

DocumentLoader is a ViewModel scoped to MainActivity: it survives a configuration change and is there before anything asks it for a document, so the queue that held work until the binder arrived has nothing left to hold. The <service> element goes with it.

The mutable Options bag was the baton one loader passed to the next, so it splits: DocumentRequest is what the user asked for, IdentifiedFile the cached copy it turned out to be, LoadedDocument the two plus the parts to show. fileExists goes with it — holding an IdentifiedFile is the answer — along with LoaderType and the already dead Options.limit.

Net −717 lines.

Worth a look in review

  • The saved-state guard. A bundle written by the shipped version can name FileLoader$Result, and Bundle reads its whole map at the first access, so one stale entry takes the rest with it. DocumentFragment.restore is guarded rather than each retired name being mapped — losing the reopened document beats throwing on launch. This is what retires LoaderType.ofParcelled and its test.
  • reload is not load. Entering or leaving edit mode, and retrying with a password, render the copy already in the cache. Sending them through load would re-read and re-identify the file and move it up the recent list on every toggle.
  • Core init is posted, not called. Odr.identify() loads the native library; that is not work for the thread putting the first frame up. Everything reaching the core is posted to the background thread anyway, so FIFO keeps the ordering.
  • One behaviour change: an empty file now reports back with no IdentifiedFile, so "Open With" offers the uri the user picked rather than a zero byte copy of it.
  • Analytics: loader_success_* / loader_error_* named a stage that no longer exists and fired alongside load_success / load_error; they collapse into those. load_odf_error_expected goes — that check is now the onUnsupported split itself. The screen name loses its constant CORE_ prefix.

Verification

  • All three flavors build; unit tests, lint and spotless clean.
  • 67 instrumented tests pass, up from 61. DocumentParcelTest is new and covers the parcel round trip nothing else did — testDocumentSurvivesRecreation restores from the view model, which never parcels.
  • tools/render-sweep over 60 corpus documents, diffed against a build of main: identical on every document — same signal, same text node count, same liveness.

Not reachable by hand on an emulator: the process-death restore. am kill plus a launcher relaunch gives a fresh task with a null savedInstanceState in both builds, so the parcel test and the guard stand in for it.


Unrelated, left alone: tools/render-sweep/render-sweep.sh still explains at length that it never taps anything because the app might offer to upload a document. That offer went away in #581.

🤖 Generated with Claude Code

With one loader left there is no chain to drive, so the machinery built for four
goes and the work it was carrying moves to components named after it.

DocumentLoader replaces LoaderService, the FileLoader base and LoaderServiceQueue.
It is a ViewModel scoped to MainActivity: it survives a configuration change and is
there before anything asks it for a document, so the queue that held work until the
binder arrived has nothing left to hold. Under it, FileCache stores the working copy,
FileIdentifier names and types it, CoreLoader renders it and DocumentSaver writes it
back - the last two lifted out of MetadataLoader and LoaderService unchanged.

The mutable Options bag was the baton one loader passed to the next, so it splits:
DocumentRequest is what the user asked for, IdentifiedFile the cached copy it turned
out to be, LoadedDocument the two plus the parts to show. fileExists goes with it -
holding an IdentifiedFile is the answer - along with LoaderType and the already dead
Options.limit.

A bundle written by the shipped version can name FileLoader$Result, and Bundle reads
its whole map at the first access, so the restore in DocumentFragment is guarded
rather than each retired name being mapped. Losing the reopened document beats
throwing on launch, and DocumentParcelTest covers the round trip that nothing else
did: the recreation test restores from the view model, which never parcels.

An empty file now reports back with no IdentifiedFile, so "Open With" offers the uri
the user picked rather than a zero byte copy of it. Nothing else changes for the user:
a render sweep over 60 documents matches the previous build on every one, signal, text
and all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018y4srZKc2SQa7JRYcDbTcc
@andiwand
andiwand marked this pull request as ready for review August 10, 2026 21:46
@andiwand
andiwand merged commit 912bba5 into main Aug 10, 2026
7 checks passed
@andiwand
andiwand deleted the dissolve-the-loader-chain branch August 10, 2026 22:09
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