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

  1. Canal de Plataforma (Platform Channel): Es el medio de comunicación entre Flutter y el código nativo.
  2. Método de Canal (Method Channel): Permite invocar métodos nativos desde Dart y viceversa.
  3. Canal de Eventos (Event Channel): Permite recibir flujos de datos desde el código nativo hacia Dart.
  4. 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

  1. Configurar el Proyecto Flutter:

    • Crear un nuevo proyecto Flutter.
    • Añadir el código Dart para invocar el método nativo.
  2. 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.
  3. 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 y Info.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.

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

Módulo 2: Conceptos Básicos de Programación en Dart

Módulo 3: Widgets en Flutter

Módulo 4: Gestión de Estado

Módulo 5: Navegación y Enrutamiento

Módulo 6: Redes y APIs

Módulo 7: Persistencia y Almacenamiento

Módulo 8: Conceptos Avanzados de Flutter

Módulo 9: Pruebas y Depuración

Módulo 10: Despliegue y Mantenimiento

Módulo 11: Flutter para Web y Escritorio

© Copyright 2024. Todos los derechos reservados