CarPlay is a platform implemented in some cars which allows users to connect their iPhones to the car’s infotainment center. It was first released in March 2014, only supporting proprietary apps, and over the years Apple opened the framework to allow third party apps.
Once connected, users can access some navigation, music, phone and messaging apps, all designed using common UI components in order to make apps similar and, therefore, easier to use and less distracting while driving.
CarPlay can be thought of as a second screen which has audio capabilities, but all the processing and execution of the app ’s done in the phone, so you can focus only on creating the UI, while continuing to use the same code as your app does.
In order to expand the usage of Uforia to other devices, the product owner decided to start developing this feature since many of our users listen to their radio streams in their daily commutes and CarPlay is available in most US vehicles.
Let's do a deep dive into the solution itself. The first step in this road was to get Apple’s Entitlement to be a CarPlay enabled app. Unlike other app entitlements (like Notifications, Associated Domains, Siri, among others), which can be added at will, the CarPlay entitlement must be approved by Apple. Without it, you can start developing the integration but the app can’t be installed in a physical device, hence uploaded to the App Store.
The problem with this entitlement is that Apple usually takes about 1 month to respond, and you can’t take for granted that it’s going to be approved. But given that Uforia has almost 200k iOS users, we were optimistic that we’d get a successful response. So we started with development and, after 1 month, we've received the good news.
CarPlay works as a second screen of the main app. Therefore, before starting any development, we needed to prepare the arena: we had to migrate from having only an “AppDelegate”, written in Obj-C, handling all the lifecycle events of the app, to have one AppDelegate and two scene delegates: one for the phone, and one for CarPlay, all made in Swift.
Using Scene Delegates is one of the latest updates in terms of UI management in iOS. It started with iOS 13 (2019), and allowed an app to have multiple screens. Each Scene Delegate corresponds to one screen and implements the core methods you use to respond to lifecycle events occurring within that scene. Originally, our AppDelegate had all the React Native Bridge initialization logic (the bridge provides communication between React Native and iOS); creation of the root view controller; initialization of some SDKs (like Firebase, Crashlytics, etc) and continuation of user activities (Deeplinks, Notifications, Siri commands, etc). With this new approach, our new AppDelegate kept all this logic to keep support for versions previous to iOS 13, but we had to copy most of them in the phone SceneDelegate.
Once we finished with this migration, we were back at the starting point, but now we could focus on creating the CarPlaySceneDelegate and writing all the logic to populate the UI. The most difficult part of this process was the very poor information available online, since almost all of the React Native apps are created using only an Obj-C AppDelegate, and don’t provide support for multiple screens. So we had to do a 100% in-house solution,, researching how to initialize the React Native Bridge with this new architecture, create the view controller and attach it to the bridge, adapt to some background / foreground events fired by CarPlay and catch up by React Native (unintentionally), etc.
The CarPlay framework provides different root templates. The one that best fitted our needs was the tab template, which consisted in a UI with some tabs that we would call Home - Radio - Music - Podcast, same as the mobile app tabs. Each tab would have a list template, and each list would have list items. This choice would become our MVP, and we would only show the root content of each tab, without any further navigation. In summary, there would be around 10 to 15 items on each tab. Creating the UI was pretty straightforward. But we needed to get the data to populate this screen. And we had a lot of services in React Native to get content from our backend. But CarPlay is done with all iOS native code, so we couldn’t reuse those services directly.
We decided to create a simple HTTP client and an endpoint that would generate all the content we needed, with new, small DTOs to reduce the overhead of these requests: we only needed an id, name, description, and image for each playable.
Leaving aside all this data retrieval logic, there was all the music playing related logic: getting the stream URL for a Radio or a Playlist song, continuing a Podcast where it was left, sending events to the Analytics platform, etc. Lots of this logic was already present in iOS code, and the good news was that we could access it directly from CarPlay, but still, we needed to do some things in React Native to prepare the phone app UI and gather some extra info before actually playing content. Duplicating code already present in React Native wasn’t an option, since it would increase maintenance complexity.
As in other parts of the app, we could use React Event Emitter to communicate between iOS and React Native via events. So we created CarPlayService in React Native. This service is subscribed to an event name that was fired every time the user selected some playable in CarPlay’s UI. When that event is received (with some attached info consisting of the type of content and its ID), we retrieve all the info we need from that playable and then the flow to play that content starts.
Over time we added to CarPlay some other features already existing in our mobile app, like “Recently Listened”, which showed the last played items on top of the list, or location related content. For these features we used the same approach: CarPlay asks ReactNative via specific events to receive this data, and React Native responds back. Then CarPlay updates the UI as needed.
In this article we discussed how to implement support for CarPlay in an existing React Native application. In our experience, integrating CarPlay to our app ended up being easier than initially thought. Even if we had some setbacks, especially in the beginning, supported by the lack of documentation, we finally came to a stable solution that has been in production since December 2022.
Since this first release was an MVP, there are a lot of features that may be included in the future, like searching for content, adding favorites, accessing to user’s library with favorited content, or improving the navigation of the menus, i.e. being able to open a Podcast and select any episode.