Introducción
Los canales de plataforma en Flutter permiten la comunicación entre el código Dart y el código nativo (Java/Kotlin para Android y Objective-C/Swift para iOS). Esto es útil cuando necesitas acceder a funcionalidades específicas del sistema operativo que no están disponibles directamente en Flutter.
Conceptos Clave
- Canal de Plataforma (Platform Channel): Es el medio de comunicación entre Flutter y el código nativo.
- Método de Canal (Method Channel): Permite invocar métodos nativos desde Dart y viceversa.
- Canal de Eventos (Event Channel): Permite recibir flujos de datos desde el código nativo hacia Dart.
- Canal Básico (Basic Message Channel): Permite el intercambio de mensajes arbitrarios entre Dart y el código nativo.
Estructura de un Canal de Plataforma
Dart (Flutter)
import 'package:flutter/services.dart'; class PlatformChannelExample { static const platform = MethodChannel('com.example.platform_channel'); Future<String> getBatteryLevel() async { try { final int result = await platform.invokeMethod('getBatteryLevel'); return 'Battery level is $result%.'; } on PlatformException catch (e) { return "Failed to get battery level: '${e.message}'."; } } }
Android (Java)
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.plugin.common.MethodChannel; import io.flutter.embedding.android.FlutterActivity; public class MainActivity extends FlutterActivity { private static final String CHANNEL = "com.example.platform_channel"; @Override public void configureFlutterEngine(FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL) .setMethodCallHandler( (call, result) -> { if (call.method.equals("getBatteryLevel")) { int batteryLevel = getBatteryLevel(); if (batteryLevel != -1) { result.success(batteryLevel); } else { result.error("UNAVAILABLE", "Battery level not available.", null); } } else { result.notImplemented(); } } ); } private int getBatteryLevel() { int batteryLevel = -1; // Código para obtener el nivel de batería return batteryLevel; } }
iOS (Swift)
import Flutter import UIKit @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { private let CHANNEL = "com.example.platform_channel" override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller : FlutterViewController = window?.rootViewController as! FlutterViewController let batteryChannel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: controller.binaryMessenger) batteryChannel.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in if call.method == "getBatteryLevel" { self.receiveBatteryLevel(result: result) } else { result(FlutterMethodNotImplemented) } }) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } private func receiveBatteryLevel(result: FlutterResult) { let device = UIDevice.current device.isBatteryMonitoringEnabled = true if device.batteryState == UIDevice.BatteryState.unknown { result(FlutterError(code: "UNAVAILABLE", message: "Battery level not available.", details: nil)) } else { result(Int(device.batteryLevel * 100)) } } }
Ejercicio Práctico
Objetivo
Crear una aplicación Flutter que obtenga el nivel de batería del dispositivo utilizando canales de plataforma.
Pasos
-
Configurar el Proyecto Flutter:
- Crear un nuevo proyecto Flutter.
- Añadir el código Dart para invocar el método nativo.
-
Implementar el Código Nativo:
- Para Android, añadir el código Java/Kotlin en
MainActivity
. - Para iOS, añadir el código Swift/Objective-C en
AppDelegate
.
- Para Android, añadir el código Java/Kotlin en
-
Probar la Aplicación:
- Ejecutar la aplicación en un dispositivo físico o emulador.
- Verificar que el nivel de batería se muestra correctamente.
Código Completo
Dart (Flutter)
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: BatteryLevelScreen(), ); } } class BatteryLevelScreen extends StatefulWidget { @override _BatteryLevelScreenState createState() => _BatteryLevelScreenState(); } class _BatteryLevelScreenState extends State<BatteryLevelScreen> { static const platform = MethodChannel('com.example.platform_channel'); String _batteryLevel = 'Unknown battery level.'; Future<void> _getBatteryLevel() async { String batteryLevel; try { final int result = await platform.invokeMethod('getBatteryLevel'); batteryLevel = 'Battery level is $result%.'; } on PlatformException catch (e) { batteryLevel = "Failed to get battery level: '${e.message}'."; } setState(() { _batteryLevel = batteryLevel; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Battery Level'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text(_batteryLevel), SizedBox(height: 20), ElevatedButton( onPressed: _getBatteryLevel, child: Text('Get Battery Level'), ), ], ), ), ); } }
Android (Java)
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.plugin.common.MethodChannel; import io.flutter.embedding.android.FlutterActivity; public class MainActivity extends FlutterActivity { private static final String CHANNEL = "com.example.platform_channel"; @Override public void configureFlutterEngine(FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL) .setMethodCallHandler( (call, result) -> { if (call.method.equals("getBatteryLevel")) { int batteryLevel = getBatteryLevel(); if (batteryLevel != -1) { result.success(batteryLevel); } else { result.error("UNAVAILABLE", "Battery level not available.", null); } } else { result.notImplemented(); } } ); } private int getBatteryLevel() { int batteryLevel = -1; // Código para obtener el nivel de batería return batteryLevel; } }
iOS (Swift)
import Flutter import UIKit @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { private let CHANNEL = "com.example.platform_channel" override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller : FlutterViewController = window?.rootViewController as! FlutterViewController let batteryChannel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: controller.binaryMessenger) batteryChannel.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in if call.method == "getBatteryLevel" { self.receiveBatteryLevel(result: result) } else { result(FlutterMethodNotImplemented) } }) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } private func receiveBatteryLevel(result: FlutterResult) { let device = UIDevice.current device.isBatteryMonitoringEnabled = true if device.batteryState == UIDevice.BatteryState.unknown { result(FlutterError(code: "UNAVAILABLE", message: "Battery level not available.", details: nil)) } else { result(Int(device.batteryLevel * 100)) } } }
Retroalimentación y Consejos
-
Errores Comunes:
- Asegúrate de que el nombre del canal sea el mismo en Dart y en el código nativo.
- Verifica que los permisos necesarios estén configurados en el archivo
AndroidManifest.xml
yInfo.plist
para acceder a la batería. - Si usas un emulador, ten en cuenta que algunos emuladores pueden no soportar la monitorización de la batería.
-
Consejos Adicionales:
- Utiliza
try-catch
en Dart para manejar excepciones y proporcionar retroalimentación al usuario. - Mantén el código nativo lo más simple posible y delega la lógica compleja a Dart cuando sea posible.
- Utiliza
Conclusión
En esta sección, aprendiste cómo utilizar los canales de plataforma para comunicarte entre Flutter y el código nativo. Esto te permite acceder a funcionalidades específicas del sistema operativo y extender las capacidades de tus aplicaciones Flutter. En el siguiente módulo, exploraremos cómo gestionar el estado en Flutter para crear aplicaciones más dinámicas y reactivas.
Curso de Desarrollo con Flutter
Módulo 1: Introducción a Flutter
- ¿Qué es Flutter?
- Configuración del Entorno de Desarrollo
- Entendiendo la Arquitectura de Flutter
- Creando Tu Primera App con Flutter
Módulo 2: Conceptos Básicos de Programación en Dart
- Introducción a Dart
- Variables y Tipos de Datos
- Sentencias de Control de Flujo
- Funciones y Métodos
- Programación Orientada a Objetos en Dart
Módulo 3: Widgets en Flutter
- Introducción a los Widgets
- Widgets Stateless vs Stateful
- Widgets Básicos
- Widgets de Diseño
- Widgets de Entrada y Formularios
Módulo 4: Gestión de Estado
Módulo 5: Navegación y Enrutamiento
- Introducción a la Navegación
- Navegación Básica
- Rutas Nombradas
- Pasando Datos Entre Pantallas
- Deep Linking
Módulo 6: Redes y APIs
- Obteniendo Datos de Internet
- Parseo de Datos JSON
- Manejo de Errores de Red
- Usando APIs REST
- Integración con GraphQL
Módulo 7: Persistencia y Almacenamiento
- Introducción a la Persistencia
- Preferencias Compartidas
- Almacenamiento de Archivos
- Base de Datos SQLite
- Usando Hive para Almacenamiento Local
Módulo 8: Conceptos Avanzados de Flutter
- Animaciones en Flutter
- Custom Paint y Canvas
- Canales de Plataforma
- Isolates y Concurrencia
- Optimización de Rendimiento
Módulo 9: Pruebas y Depuración
- Introducción a las Pruebas
- Pruebas Unitarias
- Pruebas de Widgets
- Pruebas de Integración
- Técnicas de Depuración
Módulo 10: Despliegue y Mantenimiento
- Preparación para el Lanzamiento
- Construcción para iOS
- Construcción para Android
- Integración Continua/Despliegue Continuo (CI/CD)
- Mantenimiento y Actualización de Tu App