
Platform-Specific Code
While Flutter provides a rich set of cross-platform widgets, sometimes you need to tap into platform-specific APIs—for camera access, sensors, native dialogs, or system services. That’s where platform channels come in. They let you write native code in Android, iOS, Windows, macOS, or Linux and call it directly from Dart.
Learn how to call platform-specific code in Flutter using platform channels and Pigeon. This guide covers native integration for Android, iOS, Windows, macOS, and Linux with best practices for clean, maintainable architecture.
In this guide, you’ll learn how to create a custom Flutter app that calls native code across all major platforms. We’ll explore the architectural setup, supported data types, and how to use both platform channels and Pigeon for type-safe communication. You'll also understand how threading works in this setup and how to keep platform code clean and reusable—essential for building plugins or publishing packages.
Overview
Flutter allows you to write platform-specific code to access native functionality that's not available through the Flutter framework. You can do this in two primary ways:
Platform Channels: Send asynchronous messages between Dart and native code on Android, iOS, Windows, macOS, or Linux. See Call platform-specific code using platform channels.
Pigeon Package: Generate type-safe code for platform communication using a declarative Dart syntax. See the Call platform-specific code using the Pigeon package.
Flutter currently supports the following platforms and native languages:
Android: Kotlin, Java
iOS: Swift, Objective-C
Windows: C++
macOS: Objective-C
Linux: C
These tools let you integrate custom native features while maintaining Flutter’s cross-platform codebase.
Flutter’s platform channels provide a seamless way to pass messages between the Dart UI layer (client) and platform-specific native code (host). This architecture ensures communication happens asynchronously, keeping the UI responsive even during heavy operations.

On the Flutter side,
MethodChannelis used to send method calls.On the Android side, messages are received via
MethodChannel(Kotlin/Java).On the iOS side,
FlutterMethodChannel(Swift/Objective-C) is used to handle method calls and return responses.
This setup minimizes boilerplate, making it easy to build plugins or access native services.
🔄 Note: All method calls must be invoked on the platform’s main thread, even though Flutter handles communication asynchronously. For details, see the threading section.
Additionally, the channel communication is bidirectional—platforms can also call Dart methods. A practical example is the quick_actions plugin, where the native side initiates the call.
Flutter uses the StandardMessageCodec to serialize and deserialize data between Dart and native code. It supports a range of JSON-like values for seamless integration.
Here's how Dart types map to Kotlin types:
Flutter uses the StandardMessageCodec to serialize and deserialize data between Dart and native code. It supports a range of JSON-like values for seamless integration.
Here's how Dart types map to Kotlin types:
Dart Type | Kotlin Type |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dart to Native Type Mapping Across Platforms
Dart Type | Java | Kotlin | Swift | Objective-C | C++ | C (GObject) |
|---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Let me know if you’d like a downloadable format (CSV or Markdown table) or platform-specific examples for each data type usage.
Call Platform-Specific Code Using Platform Channels
This example shows how to fetch and display the current battery level across Android, iOS, Windows, macOS, and Linux using a single method call: getBatteryLevel().
You can place platform-specific code directly in your app, or package it into reusable plugins. The dual use of platform channels works the same in both cases.
Step 1: Create a new app project#
Start by creating a new app:
In a terminal run:
flutter create batterylevel
By default, our template supports writing Android code using Kotlin, or iOS code using Swift. To use Java or Objective-C, use the -i and/or -a flags:
In a terminal run:
flutter create -i objc -a java batterylevel
Step 2: Create the Flutter platform client#
The app's State class holds the current app state. Extend that to hold the current battery state.
First, construct the channel. Use a MethodChannel with a single platform method that returns the battery level.
The client and host sides of a channel are connected through a channel name passed in the channel constructor. All channel names used in a single app must be unique; prefix the channel name with a unique 'domain prefix', for example: samples.flutter.dev/battery.
Next, invoke a method on the method channel, specifying the concrete method to call using the String identifier getBatteryLevel. The call might fail—for example, if the platform doesn't support the platform API (such as when running in a simulator), so wrap the invokeMethod call in a try-catch statement.
Use the returned result to update the user interface state in _batteryLevel inside setState.
Finally, replace the build method from the template to contain a small user interface that displays the battery state in a string, and a button for refreshing the value.
Step 3: Add an Android platform-specific implementation#
Start by opening the Android host portion of your Flutter app in Android Studio:
Start Android Studio
Select the menu item File > Open...
Navigate to the directory holding your Flutter app, and select the android folder inside it. Click OK.
Open the file
MainActivity.ktlocated in the kotlin folder in the Project view.
Inside the configureFlutterEngine() method, create a MethodChannel and call setMethodCallHandler(). Make sure to use the same channel name as was used on the Flutter client side.
Add the Android Kotlin code that uses the Android battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native Android app.
First, add the needed imports at the top of the file:
Next, add the following method in the MainActivity class, below the configureFlutterEngine() method:
Finally, complete the setMethodCallHandler() method added earlier. You need to handle a single platform method getBatteryLevel(), so test for that in the call argument. The implementation of this platform method calls the Android code written in the previous step, and returns a response for both the success and error cases using the result argument. If an unknown method is called, report that instead.
Remove the following code:
And replace with the following:
Step 4: Add an iOS platform-specific implementation#
Swift | Objective-C
Start by opening the iOS host portion of your Flutter app in Xcode:Start Xcode.Select the menu item File > Open....Navigate to the directory holding your Flutter app, and select the ios folder inside it. Click OK.Add support for Swift in the standard template setup that uses Objective-C:Expand Runner > Runner in the Project navigator.Open the file AppDelegate.swift located under Runner > Runner in the Project navigator.Override the application:didFinishLaunchingWithOptions: function and create a FlutterMethodChannel tied to the channel name samples.flutter.dev/battery:
Next, add the iOS Swift code that uses the iOS battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native iOS app.
Add the following as a new method at the bottom of AppDelegate.swift:
Finally, complete the setMethodCallHandler() method added earlier. You need to handle a single platform method getBatteryLevel(), so test for that in the call argument. The implementation of this platform method calls the iOS code written in the previous step. If an unknown method is called, report that instead.
You should now be able to run the app on iOS. If using the iOS Simulator, note that it doesn't support battery APIs, and the app displays 'Battery level not available'.
Step 5: Add a Windows platform-specific implementation#
Start by opening the Windows host portion of your Flutter app in Visual Studio:
Run
flutter build windowsin your project directory once to generate the Visual Studio solution file.Start Visual Studio.
Select Open a project or solution.
Navigate to the directory holding your Flutter app, then into the build folder, then the windows folder, then select the
batterylevel.slnfile. Click Open.
Add the C++ implementation of the platform channel method:
Expand batterylevel > Source Files in the Solution Explorer.
Open the file
flutter_window.cpp.
First, add the necessary includes to the top of the file, just after #include "flutter_window.h":
Edit the FlutterWindow::OnCreate method and create a flutter::MethodChannel tied to the channel name samples.flutter.dev/battery:
Next, add the C++ code that uses the Windows battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native Windows application.
Add the following as a new function at the top of flutter_window.cpp just after the #include section:
Finally, complete the setMethodCallHandler() method added earlier. You need to handle a single platform method, getBatteryLevel(), so test for that in the call argument. The implementation of this platform method calls the Windows code written in the previous step. If an unknown method is called, report that instead.
Remove the following code:
And replace with the following:
You should now be able to run the application on Windows. If your device doesn't have a battery, it displays 'Battery level not available'.
Step 6: Add a macOS platform-specific implementation#
Start by opening the macOS host portion of your Flutter app in Xcode:
Start Xcode.
Select the menu item File > Open....
Navigate to the directory holding your Flutter app, and select the macos folder inside it. Click OK.
Add the Swift implementation of the platform channel method:
Expand Runner > Runner in the Project navigator.
Open the file
MainFlutterWindow.swiftlocated under Runner > Runner in the Project navigator.
First, add the necessary import to the top of the file, just after import FlutterMacOS:
Create a FlutterMethodChannel tied to the channel name samples.flutter.dev/battery in the awakeFromNib method:
Next, add the macOS Swift code that uses the IOKit battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native macOS app.
Add the following as a new method at the bottom of MainFlutterWindow.swift:
Finally, complete the setMethodCallHandler method added earlier. You need to handle a single platform method getBatteryLevel(), so test for that in the call argument. The implementation of this platform method calls the macOS code written in the previous step. If an unknown method is called, report that instead.
You should now be able to run the application on macOS. If your device doesn't have a battery, it displays 'Battery level not available'.
Step 7: Add a Linux platform-specific implementation#
For this example you need to install the upower developer headers. This is likely available from your distribution, for example with:
Start by opening the Linux host portion of your Flutter app in the editor of your choice. The instructions below are for Visual Studio Code with the "C/C++" and "CMake" extensions installed, but can be adjusted for other IDEs.
Launch Visual Studio Code.
Open the linux directory inside your project.
Choose Yes in the prompt asking:
Would you like to configure project "linux"?. This enables C++ autocomplete.Open the file
runner/my_application.cc.
First, add the necessary includes to the top of the file, just after #include <flutter_linux/flutter_linux.h>:
Add an FlMethodChannel to the _MyApplication struct:
Make sure to clean it up in my_application_dispose:
Edit the my_application_activate method and initialize battery_channel using the channel name samples.flutter.dev/battery, just after the call to fl_register_plugins:
Next, add the C code that uses the Linux battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native Linux application.
Add the following as a new function at the top of my_application.cc just after the G_DEFINE_TYPE line:
Finally, add the battery_method_call_handler function referenced in the earlier call to fl_method_channel_set_method_call_handler. You need to handle a single platform method, getBatteryLevel, so test for that in the method_call argument. The implementation of this function calls the Linux code written in the previous step. If an unknown method is called, report that instead.
Add the following code after the get_battery_level function:
You should now be able to run the application on Linux. If your device doesn't have a battery, it displays 'Battery level not available'.
Call platform-specific code using the Pigeon package
You can use the Pigeon package as an alternative to Flutter's platform channel APIs to generate code that sends messages in a structured, type-safe manner. The workflow for Pigeon looks like this:
The Flutter app sends structured type-safe messages to its host, the non-Dart portion of the app, over a platform channel.
The host listens on the platform channel, and receives the message. It then calls into any number of platform-specific APIs using the native programming language and sends a response back to the client, the Flutter portion of the app.
Using this package eliminates the need to match strings between host and client for the names and data types of messages. It supports nested classes, grouping messages into APIs, generation of asynchronous wrapper code, and sending messages in either direction. The generated code is readable and guarantees there are no conflicts between multiple clients of different versions.
With Pigeon, the messaging protocol is defined in a subset of Dart that then generates messaging code for Android, iOS, macOS, or Windows. For example:
You can find a complete example and more information on the pigeon page on pub.dev.
Channels and platform threading
When invoking channels on the platform side destined for Flutter, invoke them on the platform's main thread. When invoking channels in Flutter destined for the platform side, either invoke them from any Isolate that is the root Isolate, or that is registered as a background Isolate. The handlers for the platform side can execute on the platform's main thread or they can execute on a background thread if using a Task Queue. You can invoke the platform side handlers asynchronously and on any thread.
infoNote
On Android, the platform's main thread is sometimes called the "main thread", but it is technically defined as the UI thread. Annotate methods that need to be run on the UI thread with @UiThread. On iOS, this thread is officially referred to as the main thread.
Use plugins and channels from a background isolate#
Plugins and channels can be used by any Isolate, but that Isolate has to be a root Isolate (the one created by Flutter) or registered as a background Isolate for a root Isolate.
The following example shows how to register a background Isolate in order to use a plugin from a background Isolate.
Execute channel handlers on a background thread (Android)#
In order for a channel's platform side handler to execute on a background thread on an Android app, you must use the Task Queue API.
Execute channel handlers
Execute channel handlers on a background thread (iOS)#
In order for a channel's platform side handler to execute on a background thread on an iOS app, you must use the Task Queue API.
SwiftObjective-C swift
Jump to the UI thread (Android)#
To comply with channels' UI thread requirement, you might need to jump from a background thread to Android's UI thread to execute a channel method. In Android, you can accomplish this by post()ing a Runnable to Android's UI thread Looper, which causes the Runnable to execute on the main thread at the next opportunity.
Supplementals
Common channels and codecs#
The following is a list of some common platform channel APIs that you can use to write platform-specific code:
MethodChannelfor Flutter: A named channel that you can use to communicate with platform plugins using asynchronous method calls. By default this channel uses theStandardMessageCodeccodec. This channel is not type safe, which means calling and receiving messages depends on the host and client declaring the same arguments and data types in order for messages to work.BasicMessageChannelfor Flutter: A named channel that supports basic, asynchronous message passing, using a supported message codec. Not type safe.Engine Embedder APIs for Platforms: These platform-specific APIs contain platform-specific channel APIs.
You can create your own codec or use an existing one. The following is a list of some existing codecs that you can use with platform-specific code:
StandardMessageCodec: A commonly used message codec that encodes and decodes a wide range of data types into a platform-agnostic binary format for transmission across platform channels. The serialization and deserialization of values to and from messages happens automatically when you send and receive values. For a list of supported data types, see Platform channel data types support.BinaryCodec: A message codec that passes raw binary data between the Dart side of your Flutter app and the native platform side. It does not perform any higher-level encoding or decoding of data structures.StringCodec: A message codec that encodes and decodes strings, using UTF-8 encoding.JSONMessageCodec: A message codec that encodes and decodes JSON-formatted data, using UTF-8 encoding.FirestoreMessageCodec: A message codec that handles the exchange of messages sent across the platform channel between your Flutter app and the native Firebase Firestore SDKs (on Android and iOS).
Separate platform-specific code from UI code#
If you expect to use your platform-specific code in multiple Flutter apps, you might consider separating the code into a platform plugin located in a directory outside your main application. See developing packages for details.





