Skip to content

Conversation

@DanielKuhn
Copy link
Contributor

CarPlay and Android Auto apps are expected to be able to get launched without the user having to open the respective app on the phone first - or even worse: have the phone app running at all times in order to use the CarPlay-app properly.

The example iOS-app featured in apps/example does not support launching on the CarPlay-client directly and is heavily intertwined with the phone app by presenting each CarPlay-template via a corresponding screen in the phone app and navigating to each screen when pushing the template onto the CarPlay-stack.

Since a lot of people who are using this package are wondering how to start their CarPlay-scene without having the app running in the background, I think there should be an example which illustrates this workflow and documents what's happening behind the scenes (pun intended).

This PR is an approach to addressing this issue by adding a simple CarPlay-only example app which can either get launched with the phone app open or, more importantly, WITHOUT opening the app on phone first.

Clone the original example as stand-alone-example
in order to demonstrate launching an app using this
package directly on the CarPlay-/AndroidAuto-client
without having the app running on the phone.

The stand-alone-example only renders a placeholder
text on phone and contains all it's CarPlay-logic
inside hooks which listen for
- CarPlay connection changes
- state changes (via useReducer)

The stand-alone-example CarPlay-app features
a TabTemplate as root template containing
a top-level ListTemplate with two browsable items.
Selecting a top-level item pushes a new
(sub-level) ListTemplate onto the CarPlay-stack,
which contains two non-browsable items.
Selecting a sub-level item presents a CarPlay
modal.
Do not create multiple bridges in PhoneScene.
Reuse the AppDelegates rootView and window.
When launching the CarPlay scene directly on
the CarPlay-client, create a bridge for the AppDelegate.
Again check for an already existing bridge in order to
not create multiple bridges:
If the app is already running on the phone, a bridge will
already be present on the AppDelegate.
If not: create one for the CarPlay scene.
This adds and uses an alternative approach to
initializing the app provided by @gavrichards:
- Do not call RCTAppDelegate's
application:didFinishLaunchingWithOptions
but instead cherry-pick the code from RCTAppDelegate's
application:didFinishLaunchingWithOptions
except for window and rootViewController creation
- move window and rootViewController creation to
PhoneScene since they're not needed in stand-alone
CarScene
- call initAppFromScene() both in PhoneScene and
CarScene to init the app.

This approach works for both startup scenarios,
both on Phone and on CarPlay-client.

Bonus:
The rootView property is stored in AppDelegate,
so it can be used in PhoneScene
(i.e. to pass to RNBootSplash if used)

Caveat:
The code in initAppFromScene() needs to
be adjusted to the specific version of react native
you are using!
The version used in stand-alone-example is
currently 0.71.13, so the code is taken from that
versions RCTAppDelegate and converted to Swift
(with a little help from ChatGPT for the C++ block,
so no guarantee that it works! I'm not on RN's
new architecture yet).
@DanielKuhn
Copy link
Contributor Author

DanielKuhn commented Nov 21, 2023

This is still a draft, since it covers only CarPlay for now. The Android part is simply a copy of the original example.
I will try to update the PR with a stand-alone, natively launchable Android Auto app as well. But for now this might help some people with iOS / CarPlay at least.

For a while I thought I'd be fine with the solution from @mitchdowney over at Podverse outlined in this PR (still present but commented out as "Approach 1" in the new stand-alone-example app) but after a while I found that it produced unpredictable and buggy results in combinations of phone app running/not running or starting on CarPlay first, then on phone, then killing phone again, etc...

Finally, after inspiration from @gavrichards ("Approach 2" in the new stand-alone-example app, initially outlined in this issue) and a lot of native debugging on the console of the physical device (since you don't have either Xcode nor React Native logs available when in CarPlay stand-alone mode!) I found this solution to work reliably in all circumstances / launch orders / lifecycle states.

Thanks to @gavrichards for the groundwork around initAppFromScene - and thanks to @birkir for maintaining this awesome package.

Feel free to comment and add improvements.

External display connection invokes
scene:willConnectTo:options again with the
session.role .windowExternalDisplayNonInteractive:
https://developer.apple.com/documentation/uikit/windows_and_screens/presenting_content_on_a_connected_display

This needs to be explicitly handled or rejected,
otherwise the app crashes with the error:
Terminating app due to uncaught exception 'UIViewControllerHierarchyInconsistency',
reason: 'A view can only be associated with at most one view controller at a time!"
@bitcrumb
Copy link

@DanielKuhn For what it is worth, I tried re-opening the discussion about compatibility of RN with scenes here.

@KestasVenslauskas
Copy link

Even this works & spawns the RN app but lots of apps have logic embeded inside components withing the App. So this is usefull only to show some "initial" termplate.
For example if I want to launch a player I can't because it's not rendered yet. Once I open the app it's fine.

@DanielKuhn
Copy link
Contributor Author

Even this works & spawns the RN app but lots of apps have logic embeded inside components withing the App. So this is usefull only to show some "initial" termplate. For example if I want to launch a player I can't because it's not rendered yet. Once I open the app it's fine.

I'm using this approach to render a "stand-alone" CarPlay-app. All logic for the app is encapsulated within a single component with hooks handling the CarPlay-events as outlined in the example

I'm also controlling react-native-track-player via these hooks in my production app.

@KestasVenslauskas
Copy link

@DanielKuhn Yes but this is not a real world example. In my case I use other player providers that are working only after render(). In this case I have to introduce react-native-track-player dependency and handle events with it untill the app is actually open. So my question is there a possability to make app call render and actually populate the component tree so the logic inside components would work?

@DanielKuhn
Copy link
Contributor Author

It's a very real world example with a couple 100k users 😄
When developing a CarPlay app the goal should always be that you don't need to have the phone at hand - that's the very purpose of CarPlay. Therefore rendering anything on the phone should not be necessary.
Maybe you'll find a way to implement the logic you need for your CarPlay app without the need to render anything in your phone app?
But even so: I don't see a reason why you couldn't attach app logic to the render method. After all the component in the example is rendered as well when the react native app is initialized in headless mode by starting it on CarPlay. All hooks are executed, all contexts are there... The only thing I found not working in the typescript code are setInterval and setTimeout - these are actually stopped when the phone goes into standby. As soon as I pick it up (screen lights up and the native player controls are shown on the lock screen) they start running again.

@KestasVenslauskas
Copy link

You are right about that app should not render anything. But I get weird results while app is started.
So the App component is loaded with hooks all good, but it should return other nested components that should be rendered or at least their render() method called. It looks like App render function is called but not nested components. I will try investigating a bit more later. Anyway thank you for the solution!

@alex-vasylchenko
Copy link

@DanielKuhn hi, have you already tried to update to react-native 0.74?
If you remember, my code is very similar to yours, but after the update everything broke.
I'd love to chat with you again, come to Discord

@KestasVenslauskas
Copy link

@DanielKuhn do you encounter app crash after re-connecting to carplay and clicking on any item that has onClick callback?
I do have your setup but I do experience #184 issues when re-connecting to carplay while single app instance is running.

@DanielKuhn
Copy link
Contributor Author

DanielKuhn commented Jun 10, 2024

@alex-vasylchenko I haven't updated to 0.74 yet, but as I mentioned on Discord: Every RN upgrade requires an adaptation of the AppDelegate's implementations of application:didFinishLaunchingWithOptions and initAppFromScene respectively to the new implementations of RCTAppDelegate.

@KestasVenslauskas I'm using version 2.3.0 still and I do observe blinking list elements occasionally, but only in the CarPlay Simulator, not on real devices. No crashes either.
BTW: From your screen recording I can see you're still using version 1 of "CarPlay Simulator.app" - try upgrading to version 2, available in the Downloads section of Apple's developer resources. It has some nice new features like customizable Widescreen Configs available from the system tray.

@mefjuu
Copy link

mefjuu commented Jun 27, 2024

@DanielKuhn Did you manage to run the app from Android Auto directly (without running the app on mobile device first)? There is nothing but "RNCarPlay loading..." screen in such scenario.

@DanielKuhn
Copy link
Contributor Author

@DanielKuhn Did you manage to run the app from Android Auto directly (without running the app on mobile device first)? There is nothing but "RNCarPlay loading..." screen in such scenario.

I'm not an Android developer, therefore I cannot say whether it is even possible to start a react native app in headless mode directly from Android Auto. Actually that's the main reason this pull request is still a draft.

What we did in our production app was to require the "draw over apps"-permission in Android, so that the app can be started while the phone is actually locked. This way, while not headless like in iOS, the app can still handle all events coming from Android Auto.

@mefjuu
Copy link

mefjuu commented Jul 26, 2024

Has anyone applied these changes to RN 0.74.x-based project?

@gavrichards
Copy link

Hmm yes I have been trying things similar to that. But then in my IDE I get this:
CleanShot 2025-06-13 at 15 59 20@2x
and I'm getting a build error relating to this for Android too.

@DanielKuhn
Copy link
Contributor Author

Hm - I just checked my CarPlay module and my imports look like this:

import {
  CarPlay,
  ListTemplate,
  ListTemplateConfig,
  TabBarTemplate,
  TabBarTemplateConfig
} from "react-native-carplay/src"
import { ListSection } from "react-native-carplay/src/interfaces/ListSection"
import { ListItemUpdate } from "react-native-carplay/src/interfaces/ListItemUpdate.ts"
import { Template } from "react-native-carplay/src/templates/Template"
import { NowPlayingTemplateConfig } from "react-native-carplay/src/templates/NowPlayingTemplate"

Not sure why that is (autogenerated by IntelliJ IDEA), but it works.

Concerning Android: There's no Android code in my fork other than the (empty) example in apps: https://github.com/codevise/react-native-carplay/tree/master/packages/react-native-carplay
Maybe try deleting the node_modules directory or yarn start --reset-cache?

@gavrichards
Copy link

Concerning Android: (etc)

It's just because my codebase is doing imports from 'react-native-carplay' but the package doesn't exist, for the same reason as the IDE is saying. Not specific to Android I don't think.

The thing with your code sample is that you're importing from react-native-carplay/src rather than react-native-carplay. I presume you must've had to change to that when you started using your fork?

@DanielKuhn
Copy link
Contributor Author

DanielKuhn commented Jun 13, 2025

I presume so, too (the code is over 2 years old, not sure about the ins and outs anymore)

@lovegaoshi
Copy link

lovegaoshi commented Jun 13, 2025 via email

@lovegaoshi
Copy link

and good luck folks, just saw RN 0.80 dropped...

@gavrichards
Copy link

gavrichards commented Jun 13, 2025

In the end I decided to revert to this repository, use react-native.config.js to disable autolinking, and instead use Platform.OS === 'ios' checks wherever I refer to the CarPlay object in my code. Fortunately it wasn't too many places, and I've now managed to get the app building and running on Android. Phew.
Thanks for all your help!

@gavrichards
Copy link

@DanielKuhn have you tried my solution in production yet? I've got a few versions of my whitelabel app in the store, and unbelievable they're still suffering the issue where the screen is blank if opened on CarPlay first, despite it all working fine in development.

@DanielKuhn
Copy link
Contributor Author

@DanielKuhn have you tried my solution in production yet? I've got a few versions of my whitelabel app in the store, and unbelievable they're still suffering the issue where the screen is blank if opened on CarPlay first, despite it all working fine in development.

We haven't released the new app version (with React Native 0.79) yet, but I just gave my production TestFlight a quick spin in the CarPlay Simulator and everything looks fine.
Did you try your prod-app in CarPlay Simulator.app (from "Additional Tools for Xcode")?

@gavrichards
Copy link

Did you try your prod-app in CarPlay Simulator.app (from "Additional Tools for Xcode")?

Yes, it works perfectly fine on the CarPlay Simulator, and also when running from Xcode to my iPhone and using a CarPlay physical head unit I use for testing.

It seems to be only when released through the App Store that it stops working again.

@DanielKuhn
Copy link
Contributor Author

That sounds seriously strange. There shouldn't be any difference between a production TestFlight and the released version.
I'll let you know if I experience any issues with the release in production.

@gavrichards
Copy link

Has anyone had any more luck getting this to work in production? We're still stumped as to what's going on over here. Thanks :)

@janwiebe-jump
Copy link
Contributor

@lovegaoshi I used your CarPlay related code (SceneDelegates and AppDelegate) in my Expo SDK 53 app (Expo dev client), but when starting the phone app, i get a blank (black) screen, the Expo Dev Launcher is not visible.

When I inspect the layout in the simulator, it says that the view EXDevLauncherDeferredRCTRootView is shown.

Did you experience the same? Any ideas how to fix?

@lovegaoshi
Copy link

lovegaoshi commented Jul 2, 2025 via email

@gavrichards
Copy link

We got to the bottom of our issues. It was because the New Architecture was being unknowingly re-enabled when building for the App Store via Fastlane.

@DanielKuhn
Copy link
Contributor Author

We got to the bottom of our issues. It was because the New Architecture was being unknowingly re-enabled when building for the App Store via Fastlane.

Great that you found the root of the problem. I still wonder why this hasn't surfaced earlier in TestFlight, though, since the build is the same that's getting released to production.
Anyway: I checked my UIBackgroundModes in Info.plist and we only have "audio" and "remote-notification".
I think "external-accessory" is not required for CarPlay after all.

@DanielKuhn
Copy link
Contributor Author

and good luck folks, just saw RN 0.80 dropped...

I just upgraded to RN 0.81 and can report that the standalone-startup approach from RN 0.79 still seems to work with no further adjustments to the AppDelegate 🎉

@matico3
Copy link

matico3 commented Oct 2, 2025

So as I understand it, the standalone-startup does not work if New Architecture is enabled?

@anna-morawska
Copy link

Thanks for sharing your example, it was really helpful for getting our setup working 🙌

We’re running into one strange issue that we haven’t been able to solve.
When the CarPlay scene runs in standalone mode (the app isn’t launched on the phone), everything works perfectly as long as the phone screen is active. But the moment the screen locks, any JS async operations like fetch or Promise stop executing.

Here’s what we observe:

The CarPlay scene stays visible and interactive (UI responds to taps, animations continue).
When the user taps a list item, we trigger an async JS call to load new data and show a spinner.
While the phone is locked, the spinner stays forever — the promise never resolves.
As soon as the screen wakes (no need to unlock), all pending async actions resolve immediately and the UI updates correctly.

We confirmed it’s not network-related — even a basic setTimeout(() => console.log('done'), 2000) is delayed until the phone wakes. So it really looks like the React Native JS thread or timers get suspended when the device is locked, even though CarPlay remains in the foreground and active.

Have you or anyone else noticed this behavior when running standalone CarPlay scenes?
If yes, did you find any workaround?

@mefjuu
Copy link

mefjuu commented Oct 29, 2025

@anna-morawska Is it related to Android and/or iOS?

@DanielKuhn
Copy link
Contributor Author

DanielKuhn commented Oct 29, 2025

@anna-morawska yes, I noticed the same thing, setTimeout and setInterval are not working when the phone goes to sleep as mentioned above.
What does work are react hooks - as you can see in the example, I attached all UI-related code to a reducer and run all the business logic on the respective events. For loading data/network requests I'm using the useQuery-hook from TanStack Query, which also works fine.

This whole CarPlay thing is a bit of a mystery and always involves a lot of trial & error. Not even the Apple devs themselves seem to master it completely: I found that Live Activities on CarPlay in iOS 26 (i.e. a sports match live activity from Apple's own sports app) also get suspended when the phone screen turns off - the current minute timer stops, the score is not getting updated in the UI... As soon as I pick up the phone and the screen lights up, the Live Activity is updated in CarPlay again. I was wondering if the same behavior can be observed for newer phones which have an always-on-display (since maybe it has to do with the iOS UI-rendering cycle), but I didn't get around to checking that yet.

Anyways: Good luck on finding the right solutions for your use case, I'm sure you'll get there eventually :)

@anna-morawska
Copy link

@mefjuu It’s only an issue on iOS — we’re using this library only for CarPlay

@DanielKuhn thanks a lot for the pointers, really appreciate it :)
I tried a similar setup with useReducer and TanStack Query, but unfortunately, the behavior didn’t change — the async updates still resume only when the phone screen wakes up.

We also experimented with a few native-side tweaks (activating the audio session, enabling background modes, etc), but none of them seemed to prevent the suspension.
At this point we’re running out of ideas, so if anyone happens to have suggestions or workarounds, they’d be very much appreciated 🙏

@lovegaoshi
Copy link

not to bring any meaningful discussions, but -
android has the exact same problem. Android's background management is dictated by JavaTimerManager.kt and when onPause is called, it checks for conditions and if true all timer (settimer, network calls, etc) execution is paused. so for android the solution is to implement a headlessJs service.

i'm not well versed in ios but am greatly interested in a new arch implementation and workarounds to get background networking calls working.

@anna-morawska
Copy link

@DanielKuhn - I ran a few more experiments and just wanted to double-check something with you:

are you running the CarPlay scene in headless/standalone mode and with the phone screen turned off, are you able to trigger network fetches that return fresh (non-cached) data and have your business logic run normally?

That’s exactly our use case — the app needs to load new data on demand, not just from cache. It works fine when the data is cached, but as soon as we hit a screen that requires a fresh fetch, everything pauses until the screen wakes.

I’d just like to confirm that our use cases are similar. If your setup really works under those conditions, it gives me a bit of hope that there’s still something we’re missing on our side :)

@lovegaoshi - yes, that’s exactly what I was wondering - what would be the iOS equivalent of that mechanism.
And yes, you’re right, it’s possible on Android. In our setup we’re using registerHeadlessTask, and the use case where the app needs to run in headless mode and load additional data works fine there.
It’s only on iOS (CarPlay) where the same logic gets suspended once the screen turns off.

@DanielKuhn
Copy link
Contributor Author

@anna-morawska I just re-checked, I was wrong about using TanStack Query: When a tab is selected (TabBarTemplate.onTemplateSelect), I fetch new data, serialize it and update the selected template with it - and yes, the fetched data is fresh, not from cache.

@lovegaoshi
Copy link

lovegaoshi commented Oct 31, 2025

for those interested - my senses finally came back to me to put a sleep() before loading carplay to allow me to attach xcode debug into my app and catch the error message when launching via carplay. The problem/fix is described in https://community.intercom.com/mobile%2Dsdks%2D24/crash%2Don%2Dcarplay%2D2292?postid=19611#post19611 and now my app has a "functional" standalone scene setup with the new architecture (starting from carplay now properly loads the js runtime and everything inside index.js).

Now i'm having the issue that even useState/useEffect do not trigger re-render. This is different than android that now I need to make more workarounds to set up specifically carplay, but it should be workable and i'm only concerned about setTimeout now. I'll try to find some resources in the RN community.

quick edit - I tested a simple fetch inside index.js and can see the json results printed when launched solely via carplay. I'm likely to get a workable setup within reach.

const testFunc = async () => {
  console.log('1234');
  const req = await fetch('https://jsonplaceholder.typicode.com/todos/1');
  console.log('2345');
  const res = await req.json();
  console.log('3456', res);
};

@lovegaoshi
Copy link

lovegaoshi commented Nov 2, 2025

while my app still has many bugs regarding carplay (the now playing template doesnt seem to sync well; if launched via carplay, safeview magically doesnt work; my lottie animation acts weird), i think the fundamental problems are clear now (launch from carplay doesnt crash, network req works). Heres my setup with RN 0.81 + new arch. The initialization is inspired and stolen from this PR. The new arch scene setup is stolen from jellify.

With this method all functions in your apps entry point (index.js) and the registered component (<App>) will "render" - the hooks inside <App> will function properly but the components returned in <App> will NOT render. As a result hooks kinda works: any hooks not directly in <App> will NOT render. I'm limited in ios resources and I can only test with ios and carplay emulators with ios locked, and network requests all seem to work fine. Comments welcome on testing with real devices/carplay unit.

the long delay from carplay launch is due to a sleep(10) I put in to give me time to attach debug process.

Screen.Recording.2025-11-01.at.7.23.13.PM.mov

@anna-morawska
Copy link

hey @lovegaoshi! Thank you for sharing this! Your setup is a bit different from mine, but I’ll take a closer look.

Regarding fetch — unfortunately, you won’t be able to reproduce the issue with suspended promises on the simulator. The simulator’s lock feature doesn’t behave the same way as on a physical device.

From Apple’s documentation: “For example, you can’t use Simulator to test when iOS locks the iPhone.”
Using the CarPlay Simulator

@lovegaoshi
Copy link

lovegaoshi commented Nov 5, 2025 via email

@anna-morawska
Copy link

Funny coincidence you’re asking — I actually tried exactly that today, and it worked! 🎉
I built a native module and replaced the JS fetch with it for iOS. It still needs some real-device testing with a car (I’m testing on a physical phone but using the CarPlay simulator), but it’s working on my end so far — looks really promising!

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.

10 participants