Killed-State Call Lifecycle Fixes
Killed-State Call Lifecycle Fixes
We have successfully resolved the issues surrounding incoming calls received while the app is in a killed (quit) state. The solution ensures a seamless transition from answering a native notification to speaking in the Agora CallRoomScreen, with proper cleanup of native UI and navigation stacks.
🚀 Key Issues Resolved
1. Headless Hook & API Failures
- Problem: When the app was killed, the headless JS context attempted to confirm the "Answer" action via
acceptCall. However,CallApirequired React context (Providers), which didn't exist yet, causing the API to fail and the caller to drop the call after a few seconds. - Solution: Implemented a Headless Fallback in
CallApi.js. It now detects when React isn't ready and usesapiClientdirectly, fetching the user's authentication token fromAsyncStorageto notify the backend immediately.
2. The "Second Popup" & Setup Cache
- Problem:
RNCallKeepsetup often fails in headless mode because no AndroidActivityexists. OursetupPromisewas caching this failure, causing all subsequent retry attempts in the main app to fail instantly without actually retrying. - Solution:
- Updated
setupCallKeepto reset its promise on failure, allowing the main app to successfully retry. - Added an exponential backoff retry loop (500ms, 1500ms, 3000ms) during app initialization.
- Added a best-effort direct native UI dismissal in the headless context.
- Updated
3. Call Dropping due to Unmounting
- Problem: During cold boot, the Deep Link would open
CallRoomScreen. In parallel,SplashScreenwould finish loading and executenavigation.replace(SCREEN.DRAWER). This swallowed theCallRoomScreen, unmounting it and triggering Agora'sleaveChannel, which killed the call. - Solution: Refactored
SplashScreen.jsto detect if a call has already been navigated to. If so, it now returns early and does nothing, allowing theCallRoomScreento remain safely on top of the stack.
4. Navigation Stack Stranding
- Problem: After a cold-boot call ended,
navigation.goBack()would take the user back to theSplashScreen, which was frozen because it had skipped its transition to the Home screen. - Solution: Upgraded
safeGoBackinCallRoomScreen.jswith Stack Introspection. It now checks the navigation state before popping; if the screen underneath is the Splash screen or the stack is empty, it resets the entire root to theDrawerNavigator.
5. Deep Link Logic Safety
- Problem: Native "End Call" events were firing deep links that
App.jswas interpreting as requests to navigate to the call screen. - Solution: Modified
handleDeepLinkinApp.jsto explicitly check for theaction=acceptparameter. Action strings forendare now ignored for navigation purposes.
📱 Verification Results
| Scenario | Behavior | Result |
|---|---|---|
| App Killed -> Receive Call -> Accept | UI opens directly to CallRoom; API confirms instantly; Caller stays connected. | ✅ Fixed |
| Call Room Active -> Caller Ends | CallRoom closes and user is redirected to Home Drawer (not Splash). | ✅ Fixed |
| Stale Native UI | Persistent notifications are dismissed automatically within 1-2 seconds of app boot. | ✅ Fixed |
| UI Reset Flash | SplashScreen transition is bypassed during active calls, preventing the UI "flicker". | ✅ Fixed |