Flutter'da Platform Channels: iOS ve Android Native Entegrasyon
Flutter, platform bağımsız UI çizer; ancak kamera, Bluetooth, biyometrik kimlik doğrulama veya özel platform API'leri gibi yerel işlevlere erişmek için Platform Channels kullanılır. Dart kodu ile native (Kotlin/Swift) kod arasında mesaj köprüsü kurar.
MethodChannel: Tek Seferlik Çağrılar
En yaygın kullanım şeklidir. Dart tarafından bir metot çağrısı yapılır, native taraf yanıt döner:
// Flutter (Dart) tarafı
class BatteryService {
static const _channel = MethodChannel('com.example.app/battery');
Future<int> getBatteryLevel() async {
try {
final level = await _channel.invokeMethod<int>('getBatteryLevel');
return level ?? -1;
} on PlatformException catch (e) {
debugPrint('Pil bilgisi alınamadı: ${e.message}');
return -1;
}
}
Future<bool> isCharging() async {
return await _channel.invokeMethod<bool>('isCharging') ?? false;
}
}
// UI'da kullanım
class BatteryWidget extends StatefulWidget {
const BatteryWidget({super.key});
@override
State<BatteryWidget> createState() => _BatteryWidgetState();
}
class _BatteryWidgetState extends State<BatteryWidget> {
final _service = BatteryService();
int _level = 0;
bool _charging = false;
@override
void initState() {
super.initState();
_loadBattery();
}
Future<void> _loadBattery() async {
final level = await _service.getBatteryLevel();
final charging = await _service.isCharging();
if (mounted) setState(() { _level = level; _charging = charging; });
}
@override
Widget build(BuildContext context) => ListTile(
leading: Icon(_charging ? Icons.battery_charging_full : Icons.battery_std),
title: Text('Pil: $_level%'),
subtitle: Text(_charging ? 'Şarj oluyor' : 'Pilde'),
);
}
Android (Kotlin) Implementasyonu
android/app/src/main/kotlin/.../MainActivity.kt dosyasını düzenleyin:
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build
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.app/battery"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
when (call.method) {
"getBatteryLevel" -> {
val level = getBatteryLevel()
if (level != -1) result.success(level)
else result.error("UNAVAILABLE", "Pil bilgisi alınamadı", null)
}
"isCharging" -> result.success(isCharging())
else -> result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val manager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
manager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = registerReceiver(null,
IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
if (level == -1 || scale == -1) -1 else level * 100 / scale
}
}
private fun isCharging(): Boolean {
val intent = registerReceiver(null,
IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val status = intent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
return status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL
}
}
iOS (Swift) Implementasyonu
ios/Runner/AppDelegate.swift dosyasını düzenleyin:
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel(
name: "com.example.app/battery",
binaryMessenger: controller.binaryMessenger
)
batteryChannel.setMethodCallHandler { [weak self] call, result in
switch call.method {
case "getBatteryLevel":
UIDevice.current.isBatteryMonitoringEnabled = true
let level = UIDevice.current.batteryLevel
if level == -1 {
result(FlutterError(code: "UNAVAILABLE",
message: "Pil bilgisi alınamadı",
details: nil))
} else {
result(Int(level * 100))
}
case "isCharging":
UIDevice.current.isBatteryMonitoringEnabled = true
let state = UIDevice.current.batteryState
result(state == .charging || state == .full)
default:
result(FlutterMethodNotImplemented)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
EventChannel: Sürekli Veri Akışı
Sensör verileri veya gerçek zamanlı güncellemeler için EventChannel kullanın:
// Dart tarafı — Stream olarak dinle
class AccelerometerService {
static const _channel = EventChannel('com.example.app/accelerometer');
Stream<Map<String, double>> get events =>
_channel.receiveBroadcastStream().map((dynamic event) {
final map = Map<String, dynamic>.from(event as Map);
return {
'x': (map['x'] as num).toDouble(),
'y': (map['y'] as num).toDouble(),
'z': (map['z'] as num).toDouble(),
};
});
}
// Widget'ta kullanım
StreamBuilder<Map<String, double>>(
stream: AccelerometerService().events,
builder: (context, snapshot) {
if (!snapshot.hasData) return const CircularProgressIndicator();
final data = snapshot.data!;
return Text(
'X: ${data['x']!.toStringAsFixed(2)}\n'
'Y: ${data['y']!.toStringAsFixed(2)}\n'
'Z: ${data['z']!.toStringAsFixed(2)}',
);
},
)
// Android Kotlin — EventChannel handler
EventChannel(flutterEngine.dartExecutor.binaryMessenger,
"com.example.app/accelerometer")
.setStreamHandler(object : EventChannel.StreamHandler {
private var sensorManager: SensorManager? = null
private var eventSink: EventChannel.EventSink? = null
private val listener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
eventSink?.success(mapOf(
"x" to event.values[0],
"y" to event.values[1],
"z" to event.values[2]
))
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
eventSink = events
sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
sensorManager?.registerListener(listener,
sensorManager!!.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_NORMAL)
}
override fun onCancel(arguments: Any?) {
sensorManager?.unregisterListener(listener)
eventSink = null
}
})
Platform channels, Flutter'ın ulaşamadığı her native API'ye kapı açar. Channel isimlerini ters domain formatında tutun (com.sirket.uygulama/kanal) ve metodları küçük Dart servis sınıflarında sarmalayın. Böylece platform bağımlı kod uygulama kodundan ayrı kalır.