Platform Channel Development for Flutter App (Android)

TRUETECH is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.
Development and support of all types of mobile applications:
Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1 servicesAll 1735 services
Platform Channel Development for Flutter App (Android)
Complex
~3-5 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    445

Developing a Platform Channel for Flutter Applications (Android)

A Platform Channel is a bridge between Dart code in Flutter and native Android code (Kotlin/Java). It's needed when pub.dev doesn't offer a ready-made plugin: hardware-vendor-specific SDK, low-level audio work via AudioRecord, or integration with corporate MDM systems. Three channel types exist: MethodChannel (RPC), EventChannel (event stream from native to Dart), BasicMessageChannel (bidirectional arbitrary messages).

MethodChannel: Invoking Native Methods from Dart

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
    }
}

Channel name is a string. Convention: com.company_name.application/module. Mismatched names between Dart and Kotlin result in MissingPluginException at runtime with no build-time hints.

EventChannel: Event Stream to Dart

For data the native side generates continuously: sensor readings, Bluetooth GATT notifications, GPS changes.

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
    }
}

Register in MainActivity:

EventChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.app/accelerometer")
    .setStreamHandler(SensorStreamHandler(this))

Dart:

class AccelerometerService {
  static const _channel = EventChannel('com.example.app/accelerometer');

  Stream<Map<String, dynamic>> get accelerometerStream {
    return _channel.receiveBroadcastStream().map(
      (event) => Map<String, dynamic>.from(event as Map),
    );
  }
}

// Usage:
AccelerometerService().accelerometerStream.listen((data) {
  setState(() {
    x = data['x'] as double;
    y = data['y'] as double;
  });
});

Data Types: What Passes Through the Channel

Platform Channel automatically converts between Dart and Kotlin the following types:

Dart Kotlin
null null
bool Boolean
int Int / Long
double Double
String String
Uint8List ByteArray
List List<Any?>
Map HashMap<Any, Any?>

Important: int in Dart converts to Int if it fits, otherwise Long. If the native side returns Int and Dart expects int, no problem. But if Kotlin returns Double and Dart expects int, you get a TypeError. Explicit type checking on the native side is mandatory.

Extracting into a Separate Plugin

To reuse Platform Channel across multiple projects, package it as a Flutter Plugin:

flutter create --template=plugin --platforms=android my_nfc_plugin

Entry point is a class implementing FlutterPlugin:

class MyNfcPlugin : FlutterPlugin, MethodCallHandler {
    private lateinit var channel: MethodChannel

    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(binding.binaryMessenger, "my_nfc_plugin")
        channel.setMethodCallHandler(this)
    }

    override fun onMethodCall(call: MethodCall, result: Result) {
        // handler
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }
}

onDetachedFromEngine cleans up the handler. Without it, on Flutter hot reload the old handler stays in memory, and the next channel call fires twice.

Multithreading: The Main Pitfall

Kotlin's MethodChannel.setMethodCallHandler is called on the main thread. Any blocking operation inside causes ANR. 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 main thread—it's a Flutter requirement. withContext(Dispatchers.Main) or Handler(Looper.getMainLooper()).post { } are mandatory for responses from background threads.

Calling result.success() twice crashes the Flutter engine: Methods can only be called once. If the operation is cancelled, call result.error() or call nothing (but then Dart side waits forever). Best practice: explicitly return an error on cancellation.

Testing

Unit testing Kotlin logic without Flutter:

@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 via Flutter's integration_test package run a real Flutter app with the native part on an emulator.

Developing a Platform Channel takes 1–3 days for a simple MethodChannel with 2–4 methods. EventChannel with lifecycle management and tests takes 3–5 days. Packaging as a reusable plugin adds 1–2 days. Cost is calculated individually.