Agent skill

platform-channels

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/platform-channels

SKILL.md

📱 Skill: Platform Channels & Native Integration

📋 Metadata

Atributo Valor
ID flutter-platform-channels
Nivel 🔴 Avanzado
Versión 1.0.0
Keywords platform-channels, methodchannel, eventchannel, ffi, native, swift, kotlin
Referencia Flutter Platform Channels

🔑 Keywords para Invocación

Usa cualquiera de estos keywords en tus prompts para invocar este skill:

  • platform-channels
  • methodchannel
  • eventchannel
  • ffi
  • native-integration
  • swift-integration
  • kotlin-integration
  • @skill:platform-channels

Ejemplos de Prompts

Implementa platform-channels para acceder a funcionalidad nativa de iOS y Android
Necesito methodchannel para integrar con SDK nativo
Usa eventchannel para recibir eventos de sensores nativos
@skill:platform-channels - Integración completa con APIs nativas

📖 Descripción

Platform Channels permiten comunicación bidireccional entre Flutter (Dart) y código nativo (iOS/Android). Este skill cubre MethodChannel para llamadas síncronas/asíncronas, EventChannel para streams de eventos, y FFI para integración de bajo nivel con C/C++.

✅ Cuándo Usar Este Skill

  • Acceder a APIs nativas no disponibles en packages Flutter
  • Integrar SDKs nativos de terceros
  • Implementar funcionalidad específica de plataforma
  • Acceder a hardware (sensores, Bluetooth, NFC)
  • Optimizar rendimiento con código nativo
  • Comunicación con código legacy
  • Background tasks nativos
  • Permissions handling avanzado

❌ Cuándo NO Usar Este Skill

  • Ya existe un package Flutter maduro
  • Funcionalidad disponible en Flutter puro
  • Prototipado rápido sin código nativo
  • Cross-platform simple sin requisitos nativos

🏗️ Estructura del Proyecto

my_app/
├── lib/
│   ├── services/
│   │   ├── platform_channels/
│   │   │   ├── battery_channel.dart
│   │   │   ├── sensor_channel.dart
│   │   │   └── native_sdk_channel.dart
│   │   └── native_services.dart
│   │
│   └── main.dart
│
├── android/
│   └── app/src/main/kotlin/com/example/myapp/
│       ├── MainActivity.kt
│       ├── channels/
│       │   ├── BatteryChannel.kt
│       │   ├── SensorChannel.kt
│       │   └── NativeSDKChannel.kt
│       └── utils/
│           └── PermissionHelper.kt
│
├── ios/
│   └── Runner/
│       ├── AppDelegate.swift
│       ├── Channels/
│       │   ├── BatteryChannel.swift
│       │   ├── SensorChannel.swift
│       │   └── NativeSDKChannel.swift
│       └── Utils/
│           └── PermissionHelper.swift
│
└── test/
    └── platform_channels_test.dart

📦 Dependencias

yaml
dependencies:
  flutter:
    sdk: flutter

dev_dependencies:
  flutter_test:
    sdk: flutter
  mockito: ^5.4.4  # Para testing de platform channels

💻 Implementación

1. MethodChannel - Comunicación Bidireccional

1.1 Flutter (Dart) - Battery Level Example

dart
// lib/services/platform_channels/battery_channel.dart
import 'package:flutter/services.dart';

class BatteryChannel {
  static const platform = MethodChannel('com.example.myapp/battery');

  // Get battery level (async call to native)
  Future<int> getBatteryLevel() async {
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      return result;
    } on PlatformException catch (e) {
      print("Failed to get battery level: '${e.message}'.");
      return -1;
    } catch (e) {
      print("Unexpected error: $e");
      return -1;
    }
  }

  // Check if charging
  Future<bool> isCharging() async {
    try {
      final bool result = await platform.invokeMethod('isCharging');
      return result;
    } on PlatformException catch (e) {
      print("Failed to check charging status: '${e.message}'.");
      return false;
    }
  }

  // Get battery info (multiple values)
  Future<Map<String, dynamic>> getBatteryInfo() async {
    try {
      final Map<dynamic, dynamic> result = 
          await platform.invokeMethod('getBatteryInfo');
      
      return {
        'level': result['level'] as int,
        'isCharging': result['isCharging'] as bool,
        'technology': result['technology'] as String,
        'temperature': result['temperature'] as double,
      };
    } on PlatformException catch (e) {
      print("Failed to get battery info: '${e.message}'.");
      return {};
    }
  }

  // Call method with arguments
  Future<String> chargingStatus(int threshold) async {
    try {
      final String result = await platform.invokeMethod(
        'chargingStatus',
        {'threshold': threshold},
      );
      return result;
    } on PlatformException catch (e) {
      print("Failed to get charging status: '${e.message}'.");
      return 'Unknown';
    }
  }
}

1.2 Android (Kotlin) Implementation

kotlin
// android/app/src/main/kotlin/com/example/myapp/MainActivity.kt
package com.example.myapp

import android.content.Context
import android.content.ContextWrapper
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 BATTERY_CHANNEL = "com.example.myapp/battery"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        
        MethodChannel(
            flutterEngine.dartExecutor.binaryMessenger,
            BATTERY_CHANNEL
        ).setMethodCallHandler { call, result ->
            when (call.method) {
                "getBatteryLevel" -> {
                    val batteryLevel = getBatteryLevel()
                    if (batteryLevel != -1) {
                        result.success(batteryLevel)
                    } else {
                        result.error(
                            "UNAVAILABLE",
                            "Battery level not available.",
                            null
                        )
                    }
                }
                
                "isCharging" -> {
                    val charging = isCharging()
                    result.success(charging)
                }
                
                "getBatteryInfo" -> {
                    val info = getBatteryInfo()
                    result.success(info)
                }
                
                "chargingStatus" -> {
                    val threshold = call.argument<Int>("threshold") ?: 20
                    val status = getChargingStatus(threshold)
                    result.success(status)
                }
                
                else -> {
                    result.notImplemented()
                }
            }
        }
    }

    private fun getBatteryLevel(): Int {
        val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
        return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
    }

    private fun isCharging(): Boolean {
        val batteryStatus: Intent? = IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { filter ->
            registerReceiver(null, filter)
        }
        
        val status: Int = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
        return status == BatteryManager.BATTERY_STATUS_CHARGING ||
               status == BatteryManager.BATTERY_STATUS_FULL
    }

    private fun getBatteryInfo(): Map<String, Any> {
        val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
        val batteryStatus: Intent? = IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { filter ->
            registerReceiver(null, filter)
        }

        val level = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
        val charging = isCharging()
        val technology = batteryStatus?.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY) ?: "Unknown"
        val temperature = batteryStatus?.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0)?.toDouble()?.div(10) ?: 0.0

        return mapOf(
            "level" to level,
            "isCharging" to charging,
            "technology" to technology,
            "temperature" to temperature
        )
    }

    private fun getChargingStatus(threshold: Int): String {
        val level = getBatteryLevel()
        val charging = isCharging()

        return when {
            charging && level >= 100 -> "Fully Charged"
            charging -> "Charging ($level%)"
            level <= threshold -> "Low Battery ($level%)"
            else -> "Not Charging ($level%)"
        }
    }
}

1.3 iOS (Swift) Implementation

swift
// ios/Runner/AppDelegate.swift
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    private let BATTERY_CHANNEL = "com.example.myapp/battery"
    
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        let controller = window?.rootViewController as! FlutterViewController
        
        let batteryChannel = FlutterMethodChannel(
            name: BATTERY_CHANNEL,
            binaryMessenger: controller.binaryMessenger
        )
        
        batteryChannel.setMethodCallHandler { [weak self] (call, result) in
            guard let self = self else { return }
            
            switch call.method {
            case "getBatteryLevel":
                self.receiveBatteryLevel(result: result)
                
            case "isCharging":
                result(self.isCharging())
                
            case "getBatteryInfo":
                result(self.getBatteryInfo())
                
            case "chargingStatus":
                if let args = call.arguments as? [String: Any],
                   let threshold = args["threshold"] as? Int {
                    result(self.getChargingStatus(threshold: threshold))
                } else {
                    result(self.getChargingStatus(threshold: 20))
                }
                
            default:
                result(FlutterMethodNotImplemented)
            }
        }
        
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    private func receiveBatteryLevel(result: FlutterResult) {
        let device = UIDevice.current
        device.isBatteryMonitoringEnabled = true
        
        guard device.batteryState != .unknown else {
            result(FlutterError(
                code: "UNAVAILABLE",
                message: "Battery level not available.",
                details: nil
            ))
            return
        }
        
        let batteryLevel = Int(device.batteryLevel * 100)
        result(batteryLevel)
    }
    
    private func isCharging() -> Bool {
        let device = UIDevice.current
        device.isBatteryMonitoringEnabled = true
        
        return device.batteryState == .charging || device.batteryState == .full
    }
    
    private func getBatteryInfo() -> [String: Any] {
        let device = UIDevice.current
        device.isBatteryMonitoringEnabled = true
        
        let level = Int(device.batteryLevel * 100)
        let charging = isCharging()
        
        return [
            "level": level,
            "isCharging": charging,
            "technology": "Li-ion",  // iOS doesn't expose this
            "temperature": 0.0        // iOS doesn't expose this
        ]
    }
    
    private func getChargingStatus(threshold: Int) -> String {
        let device = UIDevice.current
        device.isBatteryMonitoringEnabled = true
        
        let level = Int(device.batteryLevel * 100)
        let charging = isCharging()
        
        switch (charging, level) {
        case (true, 100):
            return "Fully Charged"
        case (true, _):
            return "Charging (\(level)%)"
        case (false, let l) where l <= threshold:
            return "Low Battery (\(level)%)"
        default:
            return "Not Charging (\(level)%)"
        }
    }
}

2. EventChannel - Streaming de Eventos

2.1 Flutter (Dart) - Sensor Stream

dart
// lib/services/platform_channels/sensor_channel.dart
import 'package:flutter/services.dart';
import 'dart:async';

class SensorChannel {
  static const eventChannel = EventChannel('com.example.myapp/sensor');

  // Stream de datos del acelerómetro
  Stream<AccelerometerData>? _accelerometerStream;

  Stream<AccelerometerData> get accelerometerStream {
    _accelerometerStream ??= eventChannel
        .receiveBroadcastStream()
        .map((dynamic event) => _parseAccelerometerData(event))
        .handleError((error) {
          print('Error receiving accelerometer data: $error');
        });
    
    return _accelerometerStream!;
  }

  AccelerometerData _parseAccelerometerData(dynamic event) {
    final Map<dynamic, dynamic> data = event as Map;
    return AccelerometerData(
      x: data['x'] as double,
      y: data['y'] as double,
      z: data['z'] as double,
      timestamp: data['timestamp'] as int,
    );
  }

  // Stream de ubicación
  Stream<LocationData> watchLocation() {
    return eventChannel
        .receiveBroadcastStream({'type': 'location'})
        .map((dynamic event) {
          final Map<dynamic, dynamic> data = event as Map;
          return LocationData(
            latitude: data['latitude'] as double,
            longitude: data['longitude'] as double,
            accuracy: data['accuracy'] as double,
            timestamp: data['timestamp'] as int,
          );
        });
  }
}

class AccelerometerData {
  final double x;
  final double y;
  final double z;
  final int timestamp;

  AccelerometerData({
    required this.x,
    required this.y,
    required this.z,
    required this.timestamp,
  });

  @override
  String toString() => 'Accelerometer(x: $x, y: $y, z: $z, time: $timestamp)';
}

class LocationData {
  final double latitude;
  final double longitude;
  final double accuracy;
  final int timestamp;

  LocationData({
    required this.latitude,
    required this.longitude,
    required this.accuracy,
    required this.timestamp,
  });

  @override
  String toString() => 'Location($latitude, $longitude) ±${accuracy}m';
}

2.2 Android (Kotlin) - EventChannel Implementation

kotlin
// android/app/src/main/kotlin/com/example/myapp/channels/SensorChannel.kt
package com.example.myapp.channels

import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import io.flutter.plugin.common.EventChannel

class SensorChannel(private val context: Context) : EventChannel.StreamHandler {
    private var sensorManager: SensorManager? = null
    private var accelerometer: Sensor? = null
    private var eventSink: EventChannel.EventSink? = null

    override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
        eventSink = events
        
        sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
        accelerometer = sensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)

        if (accelerometer != null) {
            sensorManager?.registerListener(
                sensorEventListener,
                accelerometer,
                SensorManager.SENSOR_DELAY_NORMAL
            )
        } else {
            eventSink?.error(
                "SENSOR_UNAVAILABLE",
                "Accelerometer not available",
                null
            )
        }
    }

    override fun onCancel(arguments: Any?) {
        sensorManager?.unregisterListener(sensorEventListener)
        eventSink = null
    }

    private val sensorEventListener = object : SensorEventListener {
        override fun onSensorChanged(event: SensorEvent?) {
            event?.let {
                val data = mapOf(
                    "x" to it.values[0].toDouble(),
                    "y" to it.values[1].toDouble(),
                    "z" to it.values[2].toDouble(),
                    "timestamp" to System.currentTimeMillis()
                )
                eventSink?.success(data)
            }
        }

        override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
            // Handle accuracy changes if needed
        }
    }
}

// En MainActivity.kt, registrar el EventChannel:
// EventChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.myapp/sensor")
//     .setStreamHandler(SensorChannel(this))

2.3 iOS (Swift) - EventChannel Implementation

swift
// ios/Runner/Channels/SensorChannel.swift
import Flutter
import CoreMotion

class SensorChannel: NSObject, FlutterStreamHandler {
    private let motionManager = CMMotionManager()
    private var eventSink: FlutterEventSink?
    
    func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        self.eventSink = events
        
        guard motionManager.isAccelerometerAvailable else {
            events(FlutterError(
                code: "SENSOR_UNAVAILABLE",
                message: "Accelerometer not available",
                details: nil
            ))
            return nil
        }
        
        motionManager.accelerometerUpdateInterval = 0.1
        motionManager.startAccelerometerUpdates(to: .main) { [weak self] (data, error) in
            guard let data = data, let eventSink = self?.eventSink else { return }
            
            let sensorData: [String: Any] = [
                "x": data.acceleration.x,
                "y": data.acceleration.y,
                "z": data.acceleration.z,
                "timestamp": Int(Date().timeIntervalSince1970 * 1000)
            ]
            
            eventSink(sensorData)
        }
        
        return nil
    }
    
    func onCancel(withArguments arguments: Any?) -> FlutterError? {
        motionManager.stopAccelerometerUpdates()
        eventSink = nil
        return nil
    }
}

// En AppDelegate.swift:
// let sensorChannel = FlutterEventChannel(
//     name: "com.example.myapp/sensor",
//     binaryMessenger: controller.binaryMessenger
// )
// sensorChannel.setStreamHandler(SensorChannel())

3. FFI (Foreign Function Interface)

3.1 Configuración FFI con C/C++

dart
// lib/services/native_ffi.dart
import 'dart:ffi' as ffi;
import 'dart:io';

// Definir la signatura de la función nativa
typedef NativeAddFunc = ffi.Int32 Function(ffi.Int32 a, ffi.Int32 b);
typedef AddFunc = int Function(int a, int b);

typedef NativeStringFunc = ffi.Pointer<Utf8> Function(ffi.Pointer<Utf8> str);
typedef StringFunc = ffi.Pointer<Utf8> Function(ffi.Pointer<Utf8> str);

class NativeFFI {
  late final ffi.DynamicLibrary _dylib;

  NativeFFI() {
    // Cargar la librería nativa
    if (Platform.isAndroid) {
      _dylib = ffi.DynamicLibrary.open('libnative.so');
    } else if (Platform.isIOS) {
      _dylib = ffi.DynamicLibrary.process();
    } else {
      throw UnsupportedError('Platform not supported');
    }
  }

  // Función simple: suma de dos enteros
  int add(int a, int b) {
    final AddFunc nativeAdd = _dylib
        .lookup<ffi.NativeFunction<NativeAddFunc>>('native_add')
        .asFunction();
    
    return nativeAdd(a, b);
  }

  // Función con strings
  String processString(String input) {
    final StringFunc nativeProcess = _dylib
        .lookup<ffi.NativeFunction<NativeStringFunc>>('process_string')
        .asFunction();
    
    final inputPointer = input.toNativeUtf8();
    final resultPointer = nativeProcess(inputPointer);
    final result = resultPointer.toDartString();
    
    // Liberar memoria
    calloc.free(inputPointer);
    
    return result;
  }

  // Función compleja con structs
  ComputeResult compute(double value) {
    final ComputeFunc nativeCompute = _dylib
        .lookup<ffi.NativeFunction<NativeComputeFunc>>('compute')
        .asFunction();
    
    final result = nativeCompute(value);
    return ComputeResult(
      result: result.result,
      executionTime: result.executionTime,
    );
  }
}

// Struct para FFI
class ComputeResult extends ffi.Struct {
  @ffi.Double()
  external double result;
  
  @ffi.Int64()
  external int executionTime;
}

typedef NativeComputeFunc = ComputeResult Function(ffi.Double value);
typedef ComputeFunc = ComputeResult Function(double value);

3.2 Código C Nativo

c
// android/app/src/main/cpp/native.c
#include <jni.h>
#include <string.h>
#include <stdlib.h>

// Función simple
__attribute__((visibility("default"))) __attribute__((used))
int32_t native_add(int32_t a, int32_t b) {
    return a + b;
}

// Función con strings
__attribute__((visibility("default"))) __attribute__((used))
const char* process_string(const char* input) {
    size_t len = strlen(input);
    char* result = (char*)malloc(len + 1);
    
    // Convertir a mayúsculas
    for (size_t i = 0; i < len; i++) {
        result[i] = toupper(input[i]);
    }
    result[len] = '\0';
    
    return result;
}

// Struct result
typedef struct {
    double result;
    int64_t execution_time;
} ComputeResult;

// Función compleja
__attribute__((visibility("default"))) __attribute__((used))
ComputeResult compute(double value) {
    ComputeResult res;
    int64_t start = get_current_time_ms();
    
    // Operación costosa
    res.result = expensive_computation(value);
    
    res.execution_time = get_current_time_ms() - start;
    return res;
}

4. SDK Integration - Ejemplo Completo

4.1 Flutter Service para SDK Nativo

dart
// lib/services/native_sdk_service.dart
import 'package:flutter/services.dart';

class NativeSDKService {
  static const platform = MethodChannel('com.example.myapp/native_sdk');

  // Initialize SDK
  Future<bool> initialize(String apiKey) async {
    try {
      final bool result = await platform.invokeMethod(
        'initializeSDK',
        {'apiKey': apiKey},
      );
      return result;
    } on PlatformException catch (e) {
      print('Failed to initialize SDK: ${e.message}');
      return false;
    }
  }

  // Track event
  Future<void> trackEvent(String eventName, Map<String, dynamic> properties) async {
    try {
      await platform.invokeMethod('trackEvent', {
        'eventName': eventName,
        'properties': properties,
      });
    } on PlatformException catch (e) {
      print('Failed to track event: ${e.message}');
    }
  }

  // Get user profile
  Future<Map<String, dynamic>?> getUserProfile() async {
    try {
      final Map<dynamic, dynamic> result = 
          await platform.invokeMethod('getUserProfile');
      return result.cast<String, dynamic>();
    } on PlatformException catch (e) {
      print('Failed to get user profile: ${e.message}');
      return null;
    }
  }

  // Upload file with progress
  Stream<double> uploadFile(String filePath) async* {
    final EventChannel progressChannel = 
        EventChannel('com.example.myapp/upload_progress');
    
    // Start upload
    await platform.invokeMethod('uploadFile', {'filePath': filePath});
    
    // Listen to progress
    await for (final progress in progressChannel.receiveBroadcastStream()) {
      yield progress as double;
    }
  }
}

4.2 Android Implementation con SDK

kotlin
// android/app/src/main/kotlin/com/example/myapp/channels/NativeSDKChannel.kt
package com.example.myapp.channels

import android.content.Context
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.EventChannel
// import com.thirdparty.sdk.SDK  // SDK externo

class NativeSDKChannel(private val context: Context) {
    private var sdk: SDK? = null
    
    fun handleMethodCall(call: MethodCall, result: MethodChannel.Result) {
        when (call.method) {
            "initializeSDK" -> {
                val apiKey = call.argument<String>("apiKey")
                if (apiKey != null) {
                    val initialized = initializeSDK(apiKey)
                    result.success(initialized)
                } else {
                    result.error("INVALID_ARGS", "API key is required", null)
                }
            }
            
            "trackEvent" -> {
                val eventName = call.argument<String>("eventName")
                val properties = call.argument<Map<String, Any>>("properties")
                
                if (eventName != null) {
                    trackEvent(eventName, properties ?: emptyMap())
                    result.success(null)
                } else {
                    result.error("INVALID_ARGS", "Event name is required", null)
                }
            }
            
            "getUserProfile" -> {
                val profile = getUserProfile()
                result.success(profile)
            }
            
            "uploadFile" -> {
                val filePath = call.argument<String>("filePath")
                if (filePath != null) {
                    uploadFileWithProgress(filePath)
                    result.success(null)
                } else {
                    result.error("INVALID_ARGS", "File path is required", null)
                }
            }
            
            else -> result.notImplemented()
        }
    }
    
    private fun initializeSDK(apiKey: String): Boolean {
        return try {
            sdk = SDK.getInstance(context)
            sdk?.initialize(apiKey)
            true
        } catch (e: Exception) {
            println("SDK initialization failed: ${e.message}")
            false
        }
    }
    
    private fun trackEvent(eventName: String, properties: Map<String, Any>) {
        sdk?.trackEvent(eventName, properties)
    }
    
    private fun getUserProfile(): Map<String, Any>? {
        return sdk?.getUserProfile()?.toMap()
    }
    
    private fun uploadFileWithProgress(filePath: String) {
        // Implementar upload con callbacks de progreso
        sdk?.uploadFile(filePath) { progress ->
            // Enviar progreso a través de EventChannel
            eventSink?.success(progress)
        }
    }
}

5. Background Tasks y Permissions

5.1 Background Service

dart
// lib/services/background_service.dart
import 'package:flutter/services.dart';

class BackgroundService {
  static const platform = MethodChannel('com.example.myapp/background');

  // Start background task
  Future<bool> startBackgroundTask() async {
    try {
      final bool result = await platform.invokeMethod('startBackgroundTask');
      return result;
    } on PlatformException catch (e) {
      print('Failed to start background task: ${e.message}');
      return false;
    }
  }

  // Stop background task
  Future<void> stopBackgroundTask() async {
    try {
      await platform.invokeMethod('stopBackgroundTask');
    } on PlatformException catch (e) {
      print('Failed to stop background task: ${e.message}');
    }
  }

  // Check if task is running
  Future<bool> isTaskRunning() async {
    try {
      final bool result = await platform.invokeMethod('isTaskRunning');
      return result;
    } on PlatformException catch (e) {
      print('Failed to check task status: ${e.message}');
      return false;
    }
  }
}

5.2 Android Background Service

kotlin
// android/app/src/main/kotlin/com/example/myapp/services/BackgroundService.kt
package com.example.myapp.services

import android.app.Service
import android.content.Intent
import android.os.IBinder
import androidx.core.app.NotificationCompat

class BackgroundService : Service() {
    
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // Create notification
        val notification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("Background Task")
            .setContentText("Running...")
            .setSmallIcon(R.drawable.ic_notification)
            .build()
        
        startForeground(NOTIFICATION_ID, notification)
        
        // Start background work
        performBackgroundWork()
        
        return START_STICKY
    }
    
    override fun onBind(intent: Intent?): IBinder? = null
    
    private fun performBackgroundWork() {
        Thread {
            // Long-running operation
            while (isRunning) {
                // Do work...
                Thread.sleep(1000)
            }
        }.start()
    }
    
    companion object {
        private const val CHANNEL_ID = "background_channel"
        private const val NOTIFICATION_ID = 1
        private var isRunning = false
    }
}

🎯 Mejores Prácticas

1. Error Handling

DO: Maneja todos los errores posibles

dart
try {
  final result = await platform.invokeMethod('someMethod');
  return result;
} on PlatformException catch (e) {
  print('Platform error: ${e.code} - ${e.message}');
  // Handle specific error codes
  switch (e.code) {
    case 'UNAVAILABLE':
      // Handle unavailable
      break;
    case 'PERMISSION_DENIED':
      // Handle permission denied
      break;
  }
} on MissingPluginException catch (e) {
  print('Plugin not registered: $e');
} catch (e) {
  print('Unexpected error: $e');
}

2. Type Safety

DO: Usa tipos fuertes

dart
// Definir constantes para method names
class BatteryChannelMethods {
  static const getBatteryLevel = 'getBatteryLevel';
  static const isCharging = 'isCharging';
}

// Usar enums para error codes
enum BatteryError {
  unavailable,
  permissionDenied,
  unknown;
  
  static BatteryError fromCode(String code) {
    switch (code) {
      case 'UNAVAILABLE':
        return BatteryError.unavailable;
      case 'PERMISSION_DENIED':
        return BatteryError.permissionDenied;
      default:
        return BatteryError.unknown;
    }
  }
}

3. Memory Management

DO: Limpia recursos

dart
class SensorService {
  StreamSubscription? _subscription;
  
  void startListening() {
    _subscription = sensorChannel.accelerometerStream.listen((data) {
      // Handle data
    });
  }
  
  void dispose() {
    _subscription?.cancel();
    _subscription = null;
  }
}

4. Platform-Specific Code

DO: Usa Platform checks

dart
import 'dart:io';

Future<String> getPlatformVersion() async {
  if (Platform.isAndroid) {
    return await platform.invokeMethod('getAndroidVersion');
  } else if (Platform.isIOS) {
    return await platform.invokeMethod('getIOSVersion');
  } else {
    throw UnsupportedError('Platform not supported');
  }
}

🧪 Testing

Platform Channel Testing

dart
// test/battery_channel_test.dart
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  const MethodChannel channel = MethodChannel('com.example.myapp/battery');

  TestWidgetsFlutterBinding.ensureInitialized();

  setUp(() {
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(channel, (MethodCall methodCall) async {
      switch (methodCall.method) {
        case 'getBatteryLevel':
          return 90;
        case 'isCharging':
          return true;
        default:
          throw PlatformException(code: 'UNIMPLEMENTED');
      }
    });
  });

  tearDown(() {
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(channel, null);
  });

  test('getBatteryLevel returns correct value', () async {
    final batteryChannel = BatteryChannel();
    expect(await batteryChannel.getBatteryLevel(), 90);
  });

  test('isCharging returns correct value', () async {
    final batteryChannel = BatteryChannel();
    expect(await batteryChannel.isCharging(), true);
  });
}

🚨 Troubleshooting

Error: Method Not Implemented

dart
// Verificar que el método está registrado en ambas plataformas
// y el nombre coincide exactamente

Error: MissingPluginException

// Verificar que configuraste el channel en MainActivity/AppDelegate
// y llamaste a GeneratedPluginRegistrant.register()

Error: EventChannel No Recibe Datos

kotlin
// Android: Asegúrate de llamar a eventSink?.success() 
// en el hilo principal
runOnUiThread {
    eventSink?.success(data)
}

📚 Recursos Adicionales


Versión: 1.0.0
Última actualización: Diciembre 2025
Total líneas: 1,200+

Didn't find tool you were looking for?

Be as detailed as possible for better results