KeplerAppStateManager stays bound to a root tag that dies on the first background/foreground cycle** (VVD, SDK 0.24.9914, RN 0.83, react-native-kepler 4.0.1)

I’m building a React Native for Vega app (targeting the Fire TV Stick 4K Select) and traced some app-state behaviour on the Vega Virtual Device to what looks like a root-tag binding problem in useKeplerAppStateManager (VegaAppState in the 0.83 docs). Measurements and a repro are below. I’d like to know whether this is intended and what the supported pattern is.

Setup. One interactive component, launch-type = "singleton". A debug row re-renders every 10 s and shows: getCurrentState() from the hook’s manager; live/replayed counters from addEventListenerWithReplay on change/focus/blur/reconfigure; the tree’s RootTagContext; and, calling the KeplerAppState TurboModule directly, getCurrentState(tag) plus a change listener for tags 1, 11, 21 and 31.

Home is sent with inputd-cli button_press KEY_HOMEPAGE and the return with vmsgr send pkg://<component> from the device shell (returning through the launcher UI gives identical results). vlcm list shows the same pid and instance id across every cycle, so these are genuine warm returns.

Cold start:
mnt 1 · kas active · chg 1/0 · fcs 0/0 · blr 0/0 · rec 0/0
tag 1 · kas@ 1:active 11:unknown 21:unknown 31:unknown · xchg 11:- 21:- 31:-

After Home + return (cycle 1):
mnt 1 · kas background · chg 2/0 · fcs 1/0 · blr 1/0 · rec 0/0
tag 1 · kas@ 1:background 11:active 21:unknown 31:unknown · xchg 11:active 21:- 31:-

After a second Home + return on the same process (cycle 2):
mnt 1 · kas background · chg 2/0 · fcs 2/0 · blr 2/0 · rec 0/0
tag 1 · kas@ 1:background 11:background 21:active 31:unknown · xchg 11:background 21:active 31:-

Device log for that process:
stop tracking surface 1 (Home)
begin tracking surface 11 (return)
stop tracking surface 11 (Home)
begin tracking surface 21 (return)

What this shows

  1. Backgrounding destroys the surface and foregrounding creates a new one on the next root tag, as the AppState docs describe. But the existing React tree is re-attached rather than remounted (component state and a mount counter survive), and its RootTagContext still reads 1.
  2. useKeplerAppStateManager memoizes a manager on that context value, so after the first resume it is bound to a surface that no longer exists. getCurrentState() reports background indefinitely, and change is delivered only to the live tag: after cycle 1 the original tag never receives another change in either direction, not even background on the next Home press.
  3. focus and blur do still reach the original tag on every cycle, which is why focus looked like the resume signal in my earlier tests.
  4. reconfigure with homePressed never fired on any Home press, on any tag I listened to.
  5. Back at the app root terminates the process, as the BackHandler docs say for a singleton, rather than backgrounding it.

Questions

  1. Is recreating the surface on a new root tag while preserving the React tree the intended behaviour? If so, should RootTagContext update, and should useKeplerAppStateManager re-bind?
  2. What is the supported way to read the component’s app state after a resume? Is there an API for the current live root tag, or is moduleChange / currentModuleStates on the deprecated AppState the intended process-level signal?
  3. Should reconfigure: homePressed fire on the VVD? An injected KEY_HOMEPAGE backgrounds the app exactly as the lifecycle guide describes, but no reconfigure arrives.
  4. Does the physical Fire TV Stick 4K Select behave the same way?
  5. For “pause polling in background, refresh once on resume”, what should an app depend on, given that change stops arriving on the hook’s manager after the first cycle?

Current workaround: treat focus as resume and blur as leaving, and don’t trust getCurrentState() or change from the hook after the first background. I’d rather build on the intended contract.

App Name: In Development
App Link on Amazon Appstore N/A
SDK Version: 0.24.9914
React Native Version: 0.83

Hi @michaelfromtheoutfit,

Thank you for your detailed question about useKeplerAppStateManager / VegaAppState and the root-tag binding behavior across background/foreground cycles.

Our team is looking into the intended lifecycle contract you’ve raised and will provide an update as soon as we have more information.

In the meantime, here’s what is covered in the current documentation that may be useful as reference:

VegaAppState / useKeplerAppStateManager API reference - the per-interactive-component app-state model, the VegaAppStateManager methods (getCurrentState, addEventListener, addEventListenerWithReplay, getComponentInstance), the event set (change, focus, blur, reconfigure, displayChange), the KeplerAppStateStatus values, and the reconfigure payload ({ rootTag, reconfigureReason: ‘homePressed’ }): VegaAppState - Amazon Vega Docs

Handling Vega App Backgrounding/Foregrounding (Knowledge Base) - the general foreground/background lifecycle, ways to simulate FG/BG, and recommended cleanup-on-background steps: Handling Vega App Backgrounding/Foregrounding

AppState (process-level, for reference) - note this react-native AppState module is deprecated for Vega interactive components in favor of VegaAppState, but the page documents the process-level states: AppState - Amazon Vega Docs

The documentation describes the API surface and event semantics above, but does not explicitly specify the root-tag re-binding behavior across background/foreground cycles that your questions focus on - which is what we’re confirming with the team.

Thanks for helping us improve the Vega platform.

Warm regards,
Aishwarya