When your Flutter app needs a little Kotlin or Swift
Native code is a normal part of some Flutter projects. Here's how to scope integrations without turning a small feature into an unnecessary rewrite.
“We’re building this in Flutter. Why do we need native code?”
That’s a fair question. If the reason for choosing Flutter was to share code across Android and iOS, a request for Kotlin or Swift work can sound like the plan has changed.
Sometimes it simply means the app needs a capability that lives on the platform side. The useful conversation is about how much native work is needed and where its boundary should sit.
Start with the feature, not the workaround
Suppose your app needs to communicate with a particular device. Before choosing a package or writing a plugin, describe the interaction: discover the device, connect, send a command, receive a result, and recover if the connection ends.
That description is more useful than “add Bluetooth.” It tells the team what behavior to investigate and what you expect to demonstrate.
I’d also ask which devices and operating-system versions matter. A successful demonstration on one phone is evidence for that setup; it isn’t a reason to promise compatibility with every phone.
Flutter has a place for platform-specific code
Flutter supports calling native code through platform channels. Its documentation also describes Pigeon for generating typed communication interfaces. Android implementations can use Kotlin or Java, while iOS implementations can use Swift or Objective-C. Flutter platform integration guide
The product can keep a shared interface while delegating a specific capability to the platform implementation. That doesn’t automatically call for a complete native rewrite.
For a public example of this kind of work, my BLE plugin repository includes Dart, Kotlin, and Swift code. Treat the repository as an implementation reference and check its documented scope before deciding whether it fits your device or product.
Give the integration a small, understandable contract
I’d rather review operations such as “connect” and “disconnect” than let platform-specific objects spread through every screen.
The contract should describe inputs, results, and failure states. What happens if the user cancels? What does a disconnected device look like to the rest of the app? Can the same operation be started twice?
These questions are design work, even when the interface is only a handful of methods. A clear boundary makes it easier to change one platform’s implementation without rewriting unrelated UI code.
Make the connection state explicit
Here is a deliberately simplified contract sketch. It illustrates the information a screen needs, not the API of my public plugin or production-ready BLE code:
connect(deviceId)
-> connecting
-> connected
-> failed(reason)
connection events
-> disconnected(reason)
While connecting, the screen can show progress and prevent accidental duplicate attempts. Once connected, it can offer the supported device action. If the connection ends, it needs to stop presenting the device as ready.
A useful acceptance scenario is: connect, begin an operation, disconnect the device, and confirm that the screen reflects the interruption. Decide separately whether retrying the operation is safe. Reconnecting does not establish whether the previous command completed.
This sketch intentionally leaves out discovery, permission handling, cancellation, and protocol details. Those belong in the implementation contract once the actual device requirements are known.
Test the awkward moments
The first successful connection is a useful milestone. It shouldn’t be the final one.
For a device integration, I’d propose checking disconnection during an operation, repeated attempts, permission denial, and returning to the app after leaving it. The exact checks depend on the feature and the supported platform behavior.
For each case, decide what the user should see. A technically accurate error code is rarely a helpful instruction. “Connection lost. Move closer and reconnect” may be appropriate in one situation, while another needs a different explanation. The message should follow the actual cause rather than guess it.
What this means for your estimate
Ask the developer to separate shared UI work from platform integration and device testing. Request a working demonstration of the uncertain part early, with the supported setup recorded.
Native code can be a reasonable part of a Flutter project. The important thing is that you can understand why it exists, what it does, and how it will be maintained.
If you’re still deciding between shared and native development, start with the mobile stack comparison. If you’re already planning the build, the MVP questions can help turn the integration into a reviewable milestone.