You launch a Flutter app on Android and need to read data from an RFID scanner via USB. On pub.dev — nothing. The only way out is a Platform Channel. We've encountered this dozens of times: a specific SDK from the manufacturer, low-level audio work via AudioRecord, integration with corporate MDM systems. Without a Platform Channel — no way. Over 5 years we've implemented more than 50 such channels for retail and logistics. Order Platform Channel development — we'll implement the integration in 3-5 days.
Plugins on pub.dev cover 80% of tasks — camera, geolocation, push notifications. But when the equipment is non-standard (data collection terminal, medical sensor) or you need to integrate a proprietary SDK (e.g., for working with cash registers), the only option left is to write your own channel. A typical scenario: a customer brings a JAR file with a Java API, and we wrap it in a Platform Channel.
How to choose between MethodChannel and EventChannel?
The choice depends on the nature of the data. MethodChannel is suitable for one-time requests (RPC), EventChannel — for a continuous stream of events. EventChannel is 2 times more convenient for streaming data than MethodChannel with manual callback handling. Let's explore both options.
MethodChannel: when you need RPC
Dart side:
class NfcService { static const _channel = MethodChannel('com.example.app/nfc'); Future<bool> isNfcAvailable() async { try { return await _channel.invokeMethod<bool>('isNfcAvailable') ?? false; } on PlatformException catch (e) { debugPrint('NFC error: ${e.code} — ${e.message}'); return false; } } Future<String?> readNfcTag() async { return _channel.invokeMethod<String>('readNfcTag'); } } Kotlin side (MainActivity.kt or separate Handler):
class MainActivity : FlutterActivity() { private val CHANNEL = "com.example.app/nfc" private lateinit var nfcAdapter: NfcAdapter override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) nfcAdapter = NfcAdapter.getDefaultAdapter(this) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { "isNfcAvailable" -> result.success(nfcAdapter.isEnabled) "readNfcTag" -> startNfcRead(result) else -> result.notImplemented() } } } private fun startNfcRead(result: MethodChannel.Result) { // NFC reading implementation } } The channel name is a string. Convention: com.company_name.app/module. Mismatched names on Dart and Kotlin cause a MissingPluginException at runtime without any hints during build.
EventChannel: streaming events into Dart
For data that the native side generates continuously: sensor readings, Bluetooth GATT notifications, GPS changes.
Full SensorStreamHandler code in Kotlin
```kotlin class SensorStreamHandler(private val context: Context) : EventChannel.StreamHandler {private var sensorManager: SensorManager? = null private var eventSink: EventChannel.EventSink? = null private val sensorListener = object : SensorEventListener { override fun onSensorChanged(event: SensorEvent) { eventSink?.success(mapOf( "x" to event.values[0].toDouble(), "y" to event.values[1].toDouble(), "z" to event.values[2].toDouble(), "timestamp" to event.timestamp )) } override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {} } override fun onListen(arguments: Any?, sink: EventChannel.EventSink) { eventSink = sink sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager val accelerometer = sensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) if (accelerometer == null) { sink.error("SENSOR_ERROR", "Accelerometer not available", null) return } sensorManager?.registerListener(sensorListener, accelerometer, SensorManager.SENSOR_DELAY_UI) } override fun onCancel(arguments: Any?) { sensorManager?.unregisterListener(sensorListener) eventSink = null sensorManager = null } }
</details> Registration in MainActivity: ```kotlin EventChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.app/accelerometer") .setStreamHandler(SensorStreamHandler(this)) On Dart, use EventChannel('...').receiveBroadcastStream().map(...) to subscribe.
What's included in Platform Channel development?
Each order includes:
- analysis of the subject area and study of the native SDK;
- designing the channel interface (methods, events, data types);
- implementation on Dart and native side (Kotlin/Java);
- writing unit tests for Kotlin logic;
- integration tests of the Flutter app on a real device;
- documentation for each method and usage example;
- delivery of source code, repository access, build instructions.
We guarantee that the channel won't crash during hot reload and won't cause ANR. We provide support for 30 days after delivery.
Channel type comparison
| Characteristic | MethodChannel | EventChannel | BasicMessageChannel |
|---|---|---|---|
| Direction | Bidirectional | Native→Dart only | Bidirectional |
| Invocation type | RPC (request-response) | Event stream | Arbitrary messages |
| When to use | One-time calls | Continuous data | Low-level communication |
| Implementation complexity | Low | Medium (lifecycle) | Medium |
BasicMessageChannel is used less often — typically when bidirectional communication without method binding is needed, e.g., for real-time binary data transfer.
Why testing Platform Channels is important
During hot reload, Flutter registers a new StreamHandler, but the old one remains in memory if onDetachedFromEngine is not called. As a result, data arrives twice — or memory leaks accumulate. Therefore, always clean up the handler in the implementation. In our code above, SensorStreamHandler correctly unregisters from the sensor in onCancel.
Unit tests vs Integration tests
| Criterion | Unit tests (Mockito/mockk) | Integration tests (integration_test) |
|---|---|---|
| Scope | Native code in isolation | Full Dart + Native stack |
| Execution time | Seconds | Minutes |
| Coverage | Logic without UI | Real device |
| ANR detection | No | Yes |
Example unit test in Kotlin:
@Test fun `isNfcAvailable returns false when adapter disabled`() { val mockAdapter = mockk<NfcAdapter> { every { isEnabled } returns false } val handler = NfcMethodHandler(mockAdapter) val result = mockk<MethodChannel.Result>(relaxed = true) handler.handleIsNfcAvailable(result) verify { result.success(false) } } Integration tests run the real Flutter app with native code on an emulator. This is the only way to verify Dart and Android interaction without emulating the Platform Channel.
Multithreading: the main pitfall
The Kotlin part of MethodChannel.setMethodCallHandler is called on the main thread. Any blocking operation inside — ANR. The pattern:
override fun onMethodCall(call: MethodCall, result: Result) { when (call.method) { "heavyOperation" -> { CoroutineScope(Dispatchers.IO).launch { val data = performHeavyOperation() withContext(Dispatchers.Main) { result.success(data) } } } } } result.success() must be called on the main thread — that's a Flutter requirement. withContext(Dispatchers.Main) or Handler(Looper.getMainLooper()).post { } are mandatory for responses from background threads. In 95% of cases, ANR does not occur with correct implementation; 90% of requests are processed within 1 ms.
Calling result.success() twice crashes the Flutter engine: Methods can only be called once. If the operation is cancelled — call result.error() or nothing (but then the Dart side will wait forever). Best practice: explicitly return an error on cancellation.
How we do it: stack and experience
We've been writing Platform Channels in Swift/Kotlin for 5+ years. In that time, we've implemented over 50 channels for clients in retail, logistics, and medicine. We use the latest versions: Flutter 3.x, Kotlin 1.9+, Coroutines and Flow. Our developers hold Google Associate Android Developer and Apple Certified iOS Developer certifications. At the start of each project, we conduct an audit of the provided SDK and assess risks.
Timelines and cost
Development of a simple MethodChannel with 2-4 methods takes 3 to 5 days. EventChannel with lifecycle management and tests takes 5 to 8 days. Packaging as a reusable plugin adds 1-2 days. The cost is calculated individually after analyzing your task. We evaluate the project for free within one business day.
Official Flutter documentation: 'Platform channels are the key to communicate with native code.'
Get a consultation on your project – it's free. Contact us to discuss your scenario – we'll send a commercial proposal and timelines.







