Skip to content

feat(android-client): surface Dynamic Widget render failures - #358

Open
manapard wants to merge 1 commit into
callstackincubator:mainfrom
manapard:feat/android-dynamic-widget-render-failures
Open

manapard wants to merge 1 commit into
callstackincubator:mainfrom
manapard:feat/android-dynamic-widget-render-failures

Conversation

@manapard

@manapard manapard commented Oct 20, 2026 •

Copy link
Copy Markdown

What is this?

Android Dynamic Widgets no longer hide render failures. Before this change, when a widget's bundle could not load, the bundle threw while evaluating, render() threw, or the output could not be parsed, the error turned into a silent null. The widget then showed the build-time placeholder. A developer saw plausible but wrong content while Metro returned an error. A user whose widget had rendered fine saw it drop back to the placeholder after one bad props update, locale change or resize. Nothing on screen or in the logs explained why.

It implements the Android scope of ADR 0013 (included in this PR), which narrows #318 to a minimal version with no new public API. iOS gets the same behaviour in a follow-up PR.

voltra-render-failures-demo.mp4

How does it work?

  • Debug builds show an error view in the widget whenever a render fails: the widget id, the failing stage (bundle, evaluate, render or parse), the JS message, and the first stack frames. Tapping it reloads the bundle from Metro and renders that placement again. The bundle state is now read on every composition instead of being captured once per Glance session. Before, an unreachable Metro at session start left the widget on the placeholder until the session ended.
  • Release builds never show an error view. A failed render keeps the last successful render for that placement, else the placeholder. Each placement's last good render is stored in its own file in app storage that isn't backed up. That file is moved when the launcher restores widgets under new ids, and removed when the widget is removed or when the app clears the widget's data.
  • The native renderer now passes the JS error's message and stack to Kotlin as a typed exception, with a consumer ProGuard rule so release minification keeps the class.
  • Each failure is logged once under the VoltraDynamicWidget logcat tag. A later successful render logs a single recovery line.

Why is this useful?

Developers see a broken widget on the home screen together with the reason, and can recover with one tap after fixing the code or restarting Metro. End users keep a widget that worked instead of watching it fall back to build-time content. The change adds no public API, so the app-facing failure event can be designed later on top of the Android event channel (#314) without anything here being in the way.

@manapard
manapard force-pushed the feat/android-dynamic-widget-render-failures branch from c88c88a to 4477879 Compare October 21, 2026 13:39
Debug builds show an in-widget error view with the failing stage, the JS
message and stack, and a tap-to-retry that reloads the bundle from Metro.
Release builds keep the last successful render of each placement instead
of falling back to the placeholder. Each failure is logged once under the
VoltraDynamicWidget tag. The bundle state is now read on every composition,
so a Metro outage at session start no longer sticks for the whole session.

Implements the Android scope of ADR 0013 (narrows callstackincubator#318).
@manapard
manapard force-pushed the feat/android-dynamic-widget-render-failures branch from 4477879 to 4759429 Compare October 21, 2026 13:44
@manapard
manapard marked this pull request as ready for review October 21, 2026 14:26

This branch has not been deployed

No deployments
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