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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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';
}
}
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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()
}
}
}
}
}
Enter fullscreen mode Exit fullscreen mode
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'),
),
],
),
),
);
}
}
Enter fullscreen mode Exit fullscreen mode
Passing arguments
Dart:
final result = await _channel.invokeMethod(
'greet',
{'name': 'Nauman'},
);
Enter fullscreen mode Exit fullscreen mode
Kotlin:
"greet" -> {
val name = call.argument<String>("name") ?: "User"
result.success("Hello, $name!")
}
Enter fullscreen mode Exit fullscreen mode
Returning structured data
Kotlin can return maps:
result.success(
mapOf(
"platform" to "Android",
"version" to android.os.Build.VERSION.RELEASE
)
)
Enter fullscreen mode Exit fullscreen mode
Dart:
final Map<dynamic, dynamic> data =
await _channel.invokeMethod('deviceInfo');
print(data['platform']);
print(data['version']);
Enter fullscreen mode Exit fullscreen mode
Error handling
Native code should return meaningful errors:
result.error(
"DEVICE_ERROR",
"Unable to access device information",
null
)
Enter fullscreen mode Exit fullscreen mode
Dart:
try {
await NativeService.getPlatformMessage();
} on PlatformException catch (e) {
debugPrint(e.code);
debugPrint(e.message);
}
Enter fullscreen mode Exit fullscreen mode
Production recommendations
Avoid scattering MethodChannel calls throughout widgets. A cleaner architecture is:
UI
|
BLoC / ViewModel
|
Repository
|
NativeService
|
MethodChannel
|
Kotlin
Enter fullscreen mode Exit fullscreen mode
This keeps platform-specific code isolated and testable.
Common mistakes
Channel names do not match
These must be identical:
MethodChannel('com.example.platform/native')
Enter fullscreen mode Exit fullscreen mode
and:
private val CHANNEL = "com.example.platform/native"
Enter fullscreen mode Exit fullscreen mode
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
답글 남기기
댓글을 달기 위해서는 로그인해야합니다.