On May 15, 2025, Apple began rolling out CarPlay Ultra in new Aston Martins ordered in the US and Canada. It’s the first CarPlay that reaches past the center screen into the instrument cluster, radio, and climate, and it needs an iPhone 12 or later on iOS 18.5. For the phone app beside it, the question is which code you write once and which you write twice. For most connected car companion apps, Flutter is the better bet, and the exceptions are specific and predictable. Hyundai, Kia and Genesis have signed on for CarPlay Ultra next, so a lot more product owners are about to face that split.
I’ve spent the last several years on mobile apps that talk to cars and the boxes bolted into them: a wallbox charger app, a dealer service app, a couple of fleet tools that read data from OBD-II dongles. Every one of those projects split along the same seam. I call it the hardware boundary, the line where shared Dart code stops and Swift or Kotlin has to take over. Put that line in the right place and Flutter saves you a second app. Put it in the wrong place and you spend a year fighting plugins.
Counterpoint Research reported on March 12, 2025, that 75% of cars sold in 2024 had embedded cellular connectivity. Meanwhile, Google says, citing Apptopia data, that Flutter accounts for nearly 30% of all new free iOS apps, up from around 10% of tracked free apps in 2021. My read is that the default companion app stack is drifting toward cross-platform for that reason alone. That last part is my inference, not the reports’. What follows is a spec sheet, criterion by criterion, with a verdict on each.
Digital keys and Bluetooth LE sit right on the hardware boundary
Here’s the claim you’ll hear in almost every kickoff meeting. Someone from the hardware side usually says it first.
“Cross-platform can’t handle hardware.”
It can, just not in Dart. Flutter talks to hardware through platform channels, which are a message bridge between your Dart code and native Swift or Kotlin code running on the phone. A package like flutter_blue_plus wraps Bluetooth Low Energy scanning, pairing and GATT reads and writes (the read and write calls a phone makes to a BLE device’s data slots) for both platforms. For a wallbox, a tire pressure sensor or an OBD-II dongle you talk to while the app is on screen, that works fine. I’ve shipped it.
The trouble starts when the phone has to be the key. Passive entry means the phone opens the doors while it sits in a jacket pocket and the app isn’t running. On iOS, that relies on Core Bluetooth state restoration, which lets the system relaunch a terminated app in the background when a known device shows up. On Android, it means a foreground service with the connectedDevice type and the right Bluetooth permissions. You don’t want a Dart engine cold start in a path that has to answer in a fraction of a second, and you don’t want a third-party plugin’s maintainer deciding when your key works.
Ask about this early. It’s the question to put to any vendor, from a freelancer to Flutter development services by LITSLINK: which parts of the key flow will be written in Swift and Kotlin, and who maintains them after launch?
There’s also a ceiling no framework lifts. Apple’s Car Key in Wallet and Google’s digital car key are built with automakers on the Car Connectivity Consortium’s Digital Key spec. A dealer or fleet app doesn’t get to bolt itself onto those. If you’re building your own BLE key for a rental fleet, the key service is native code.
Edge: native, for anything that has to work with the app closed.
CarPlay and Android Auto don’t run your app’s UI anyway
This criterion gets argued the most and matters the least for the framework choice. Apple’s CarPlay Developer Guide is blunt about the split: your app picks which template to show and supplies the data, and iOS renders the information in CarPlay. You don’t draw the screen. Apple draws it.
The rules get tight fast. Every CarPlay app needs an entitlement for its category, and Apple reviews the request. EV charging and parking apps are limited to a depth of 5 templates, including the root. Fueling apps get 3.
Driving task apps get 2, or 3 on iOS 26.4 and later, and the guide says custom maps and real-time video are not possible for them. EV charging apps “can’t just be a list of EV chargers,” and they can’t show locations other than chargers on a map. Only navigation apps get a surface to draw on, and even there it’s for the map alone.
Google’s side looks similar. The Android for Cars App Library documentation lists navigation, points of interest, Internet of Things, and weather as the categories it supports, and its templates are built around driver distraction standards. Your app must fit a category before it can be listed on Google Play for Android Auto and Android Automotive OS.
So the car screen module is a thin native layer in both cases: a CarPlay scene in Swift, a CarAppService in Kotlin. Community packages such as flutter_carplay let you feed those templates from Dart, but they’re feeding the same native templates. A fully native app doesn’t get a richer car screen. It gets the same lists, grids and info panes.
Tie.
Trip tracking is a battery problem before it’s a framework problem
Background location is where companion apps quietly fail. A mileage log for a sales team, a fleet app proving where a van went, an insurance app scoring braking: all of them need the phone to record trips while the screen is off and the app is half forgotten.
Both platforms make this hard on purpose. On iOS you need the location background mode, “Always” permission, and usually the significant-change location service, which can relaunch a terminated app when the phone moves far enough. On Android 14 and later, a location foreground service must declare its type in the manifest and request the matching foreground service permission. You also can’t start one from the background without the separate background location permission, and Google Play wants a written justification before it approves that permission.
Flutter handles the common cases through mature plugins like geolocator and Transistor Software’s flutter_background_geolocation. For a dealer app that logs a test drive, that’s plenty. For an all-day telematics recorder that has to survive OS updates, aggressive Android battery savers on some phone brands, and the occasional iOS change to background rules, you’ll end up reading and patching native code anyway. Better to own it from day one.
Edge: native, narrowly, and only when trip data is the product.
Telematics APIs live in the cloud, which makes them framework-neutral
Most of the car data a third-party app touches never comes from the car directly. It comes from a server. Smartcar, for example, gives you a single REST interface across automakers: read signals such as battery level, odometer, and location, and send commands to lock the doors, set a charge limit, or start and stop charging. Fleet platforms work the same way through their cloud APIs. The phone just calls an HTTPS endpoint, gets JSON back, and draws a gauge.
Dart makes that call exactly as well as Swift does. You write the OAuth consent flow once, the polling or webhook handling once, and the battery card once. On a two-app native build, you write all of it twice and then spend a sprint reconciling why the Android range estimate rounds differently.
The exception is an OEM that ships its connected services only as a native mobile SDK, an iOS framework plus an Android library. That happens with some automaker and aftermarket telematics programs. You wrap it behind a platform channel, expose five or six methods to Dart, and move on. Annoying, not fatal.
Edge: Flutter.
One codebase puts the same app on every phone on the same day
A car brand cares about how its app looks almost as much as how its grille looks. CarPlay Ultra lets automakers ship their own themes for exactly that reason. Flutter fits that instinct because it renders its own pixels with its own engine instead of borrowing each platform’s buttons. The charging card, the service booking flow and the EV range ring look identical on an iPhone 16 and a Galaxy S25.
Release speed follows from that. One feature means one pull request, one design review and one QA pass on the shared logic. A charging network that changes its pricing screen doesn’t have to ship iOS on Tuesday and Android two weeks later because the Kotlin developer was out. Dealer groups notice this most. Their customers own whatever phone they own, and the service department doesn’t want two sets of support tickets.
I’ll admit a soft spot here. Some of the worst meetings I’ve sat through were two native teams debating whether a toggle should be blue or slightly bluer.
Edge: Flutter.
Maintenance cost follows the number of codebases
Companion apps live as long as the cars they serve, and owners keep cars for years. That makes the five-year cost matter more than the launch cost. Two native apps mean two teams, two dependency trees, two sets of OS upgrade work every September and two places for the same bug to hide.
Flutter isn’t free of upkeep. You take on framework upgrades, and the real risk is plugins. A small open-source BLE or background plugin can stall when its maintainer changes jobs, and then a new iOS release breaks it. The fix is the same as with the digital key: don’t outsource the parts that touch hardware to a hobby project. Write those native modules yourself, keep them small, and let the shared UI and business logic carry the rest.
Edge: Flutter.
Draw the hardware boundary on purpose
Here’s the spec sheet in one table. Read the verdict column first.
| Criterion | Flutter | Native | Verdict |
|---|---|---|---|
| BLE and digital key | Fine for on-screen pairing and dongles. Weak for passive entry. | Full control of state restoration and background services | Edge: native |
| CarPlay and Android Auto | Thin native layer required, templates fed from Dart | Thin native layer, same templates | Tie |
| Background location and trip tracking | Mature plugins cover casual logging | Direct control for all-day recording | Edge: native, narrowly |
| OEM and telematics SDKs | Cloud REST APIs work the same, native SDKs wrapped | Native SDKs drop straight in | Edge: Flutter |
| UI consistency and release speed | One UI, one release | Two UIs, two release trains | Edge: Flutter |
| Long-term maintenance | One codebase plus a few native modules | Two full codebases | Edge: Flutter |
Look at where native wins. Both cases sit on the hardware boundary, and both involve code that has to run when nobody is looking at the screen. That’s why the exceptions are predictable.
The practical answer for most teams is a hybrid. Build the app in Flutter: screens and the flows between them, account and payment flows, charging history, service booking, the API layer. Then write a small set of native modules on each platform and call them through platform channels. Pigeon, a code generator from the Flutter team, gives those channels typed interfaces so Dart and Swift agree on what a “vehicle status” message looks like.
A typical split on the projects I’ve worked on looks like this:
- Dart owns every screen a person taps, plus state, caching and cloud API calls.
- Swift and Kotlin own the BLE key service, the background trip recorder, the CarPlay scene and the Android Auto car app service.
- A narrow channel between them carries events like “door locked” and “trip ended.”
It’s not glamorous. It does mean one product team ships features while two small native specialists guard the edges.
The three-question test settles it in an afternoon
Run your app idea through these before anyone writes a line of code. Most teams can answer all three in a single meeting.
- Must the phone work as a key while it sits in a pocket? If yes, the key module is native, full stop. If the key is the whole product, as with a car-sharing or rental fleet app, consider going native end to end. If it’s one feature among many, stay with Flutter and wrap the key.
- Will the app record trips all day with the screen off, and is that data the thing you sell? If yes, write the trip recorder natively on each platform and keep Flutter for the rest. If you only log the odd test drive or charging session, Flutter plugins will do.
- Is most of your roadmap screens, payments, bookings and cloud data? For dealer apps, charging network apps and most fleet dashboards, the answer is yes. That’s Flutter territory, and the car screen layer is native either way.
Write the car-facing code in the car’s language, and write everything else once.







