Flutter Platform Channels: Calling Kotlin from Dart
Flutter gives you a single codebase for Android and iOS, but sometimes you need functionality that is specific to the native platform. This is where Platform Channels become useful.
In this tutorial, we will build a simple Flutter-to-Kotlin bridge using MethodChannel.
What are Platform Channels?
A platform channel allows Dart code to communicate with native Android or iOS code.
Flutter / Dart
|
| MethodChannel
v
Android / Kotlin
|
v
Native API
Flutter provides:
-
MethodChannel— request/response calls -
EventChannel— continuous streams of events -
BasicMessageChannel— asynchronous messages
For calling a Kotlin function and receiving a result, MethodChannel is usually the best choice.
Step 1: Create the Flutter project
flutter create platform_channel_demo
cd platform_channel_demo
Step 2: Create the Dart platform channel
Create lib/native_service.dart:
import 'package:flutter/services.dart';
class NativeService {
static const MethodChannel _channel =
MethodChannel('com.example.platform/native');
static Future<String> getPlatformMessage() async {
final result = await _channel.invokeMethod<String>(
'getPlatformMessage',
);
return result ?? 'No response';
}
}
The channel name must match the channel name used by Kotlin.
Step 3: Call Kotlin from MainActivity
Open:
android/app/src/main/kotlin/.../MainActivity.kt
Use:
package com.example.platform_channel_demo
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.example.platform/native"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
when (call.method) {
"getPlatformMessage" -> {
result.success("Hello from Kotlin!")
}
else -> {
result.notImplemented()
}
}
}
}
}
Step 4: Use it from Flutter
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'native_service.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String message = 'Waiting...';
Future<void> loadMessage() async {
try {
final value = await NativeService.getPlatformMessage();
if (!mounted) return;
setState(() {
message = value;
});
} on PlatformException catch (e) {
if (!mounted) return;
setState(() {
message = 'Native error: ${e.message}';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Platform Channel')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(message),
const SizedBox(height: 20),
ElevatedButton(
onPressed: loadMessage,
child: const Text('Call Kotlin'),
),
],
),
),
);
}
}
Passing arguments
Dart:
final result = await _channel.invokeMethod(
'greet',
{'name': 'Nauman'},
);
Kotlin:
"greet" -> {
val name = call.argument<String>("name") ?: "User"
result.success("Hello, $name!")
}
Returning structured data
Kotlin can return maps:
result.success(
mapOf(
"platform" to "Android",
"version" to android.os.Build.VERSION.RELEASE
)
)
Dart:
final Map<dynamic, dynamic> data =
await _channel.invokeMethod('deviceInfo');
print(data['platform']);
print(data['version']);
Error handling
Native code should return meaningful errors:
result.error(
"DEVICE_ERROR",
"Unable to access device information",
null
)
Dart:
try {
await NativeService.getPlatformMessage();
} on PlatformException catch (e) {
debugPrint(e.code);
debugPrint(e.message);
}
Production recommendations
Avoid scattering MethodChannel calls throughout widgets. A cleaner architecture is:
UI
|
BLoC / ViewModel
|
Repository
|
NativeService
|
MethodChannel
|
Kotlin
This keeps platform-specific code isolated and testable.
Common mistakes
Channel names do not match
These must be identical:
MethodChannel('com.example.platform/native')
and:
private val CHANNEL = "com.example.platform/native"
Calling native code directly from many widgets
Create a service or repository layer instead.
Blocking the UI thread
Native code should not perform long-running work on the Android main thread.
Conclusion
Platform Channels are one of Flutter's most useful escape hatches when you need native Android functionality. A clean MethodChannel abstraction lets you combine Flutter's UI productivity with Kotlin's native APIs.
Useful Links
SDK Flutter: https://github.com/v-modal/vmodal_sdk_flutter
SDK Android: https://github.com/v-modal/vmodal_sdk_android
Discord: https://discord.gg/K72z28KUx
Top comments (0)