Skip to content

Commit a7327c8

Browse files
feat: add isCarServiceRunning() to distinguish headless start sources
On Android, both Android Auto and other services (e.g. Firebase notifications) can start the app process headlessly. Until now there was no way to tell which source triggered the start. This made it impossible to skip heavy JS initialization (store, middleware) during notification-only headless starts, causing the process to stay alive unnecessarily. isCarServiceRunning() is a synchronous boolean that returns true when the native CarAppService (Android Auto) or CarPlay scene is active. It is available immediately at startup, before any component renders, allowing apps to gate their initialization in index.js using lazy require() calls. - Android: checks AndroidAutoService.instance != null - iOS: checks SceneStore.isRootModuleConnected() - Added index_with_headless.js example showing the recommended pattern Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ac1943f commit a7327c8

12 files changed

Lines changed: 121 additions & 0 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* This is an alternative entry point that demonstrates how to use
3+
* HybridAutoPlay.isCarServiceRunning() to prevent heavy JS initialization
4+
* during notification-only headless starts on Android.
5+
*
6+
* Problem:
7+
* On Android, both Android Auto and Firebase notifications can start the app
8+
* process headlessly. In both cases index.js executes and all top-level imports
9+
* are evaluated eagerly — including the Redux store, middleware, and other side
10+
* effects. These keep the process alive after the notification is handled,
11+
* wasting resources and battery.
12+
*
13+
* Solution:
14+
* Use isCarServiceRunning() to distinguish between headless starts triggered by
15+
* Android Auto / CarPlay and those triggered by other sources (e.g. notifications).
16+
* Heavy modules are loaded via lazy require() calls that only execute when actually
17+
* needed — either when the car service is running, when the user opens the app
18+
* (Activity triggers runApplication), or when AA/CP connects later.
19+
*
20+
* Scenarios:
21+
* 1. App starts first, then AA/CP connects:
22+
* - else branch taken, lazy component provider registered
23+
* - Activity renders -> provider executes, loads store/App/car listeners
24+
* - didConnect fires later -> car templates created
25+
*
26+
* 2. AA/CP starts without app open (headless):
27+
* - if branch taken, everything loads immediately
28+
* - Car UI works, phone UI mounts later if Activity starts
29+
*
30+
* 3. Notification comes in, no app, no AA/CP:
31+
* - else branch taken, but component provider never called (no Activity)
32+
* - didConnect never fires (no car)
33+
* - No require() calls execute -> store/middleware never load -> clean exit
34+
*
35+
* 4. Notification comes in, then app or AA/CP starts shortly after:
36+
* - else branch taken initially (lightweight)
37+
* - Activity or didConnect triggers lazy loading when needed
38+
*/
39+
40+
import React from 'react';
41+
import { AppRegistry } from 'react-native';
42+
import { HybridAutoPlay } from '@iternio/react-native-auto-play';
43+
import { name as appName } from './app.json';
44+
45+
let carListenersInitialized = false;
46+
47+
function initCarListeners() {
48+
if (carListenersInitialized) return;
49+
carListenersInitialized = true;
50+
const registerRunnable = require('./src/AutoPlay').default;
51+
registerRunnable();
52+
}
53+
54+
if (HybridAutoPlay.isCarServiceRunning()) {
55+
const { StateWrapper } = require('./src/state/store');
56+
const App = require('./src/App').default;
57+
58+
AppRegistry.setWrapperComponentProvider(() => StateWrapper);
59+
AppRegistry.registerComponent(appName, () => App);
60+
initCarListeners();
61+
} else {
62+
AppRegistry.registerComponent(appName, () => {
63+
const { StateWrapper } = require('./src/state/store');
64+
const App = require('./src/App').default;
65+
initCarListeners();
66+
return (props) =>
67+
React.createElement(StateWrapper, null, React.createElement(App, props));
68+
});
69+
70+
HybridAutoPlay.addListener('didConnect', initCarListeners);
71+
}

packages/react-native-autoplay/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridAutoPlay.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ class HybridAutoPlay : HybridAutoPlaySpec() {
4545
return AndroidAutoSession.getIsConnected()
4646
}
4747

48+
override fun isCarServiceRunning(): Boolean {
49+
return AndroidAutoService.instance != null
50+
}
51+
4852
override fun addListenerRenderState(
4953
moduleName: String, callback: (VisibilityState) -> Unit
5054
): () -> Unit {

packages/react-native-autoplay/ios/hybrid/HybridAutoPlay.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ class HybridAutoPlay: HybridAutoPlaySpec {
8383
return SceneStore.isRootModuleConnected()
8484
}
8585

86+
func isCarServiceRunning() throws -> Bool {
87+
return SceneStore.isRootModuleConnected()
88+
}
89+
8690
func addSafeAreaInsetsListener(
8791
moduleName: String,
8892
callback: @escaping (SafeAreaInsets) -> Void

packages/react-native-autoplay/nitrogen/generated/android/c++/JHybridAutoPlaySpec.cpp

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-native-autoplay/nitrogen/generated/android/c++/JHybridAutoPlaySpec.hpp

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-native-autoplay/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridAutoPlaySpec.kt

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-native-autoplay/nitrogen/generated/ios/c++/HybridAutoPlaySpecSwift.hpp

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-native-autoplay/nitrogen/generated/ios/swift/HybridAutoPlaySpec.swift

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-native-autoplay/nitrogen/generated/ios/swift/HybridAutoPlaySpec_cxx.swift

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-native-autoplay/nitrogen/generated/shared/c++/HybridAutoPlaySpec.cpp

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)