Escribir código personalizado específico de la plataforma
Aprende cómo escribir código personalizado específico de la plataforma en tu aplicación.
Esta guía describe cómo usar código personalizado específico de la plataforma con Flutter.
Resumen
#Puedes utilizar código específico de la plataforma en tu aplicación de Flutter. Algunas formas comunes de hacerlo incluyen:
-
Usar las API de canales de plataforma de Flutter para enviar mensajes entre Flutter y tus plataformas deseadas. Para obtener más información, consulta Llamar a código específico de la plataforma usando canales de plataforma.
-
Usar el paquete
Pigeonpara generar código específico de la plataforma con tipado seguro. Para obtener más información, consulta Llamar a código específico de la plataforma usando el paquete Pigeon.
Flutter es compatible con las siguientes plataformas y lenguajes específicos de la plataforma:
- Android: Kotlin, Java
- iOS: Swift, Objective-C
- Windows: C++
- macOS: Objective-C
- Linux: C
Perspectiva arquitectónica de los canales de plataforma
#Los mensajes se transmiten entre el cliente (interfaz de usuario) y el host (plataforma) mediante canales de plataforma, como se ilustra en este diagrama:
En el diagrama anterior, los mensajes y las respuestas se pasan
de forma asíncrona a través de canales para garantizar que la interfaz de usuario
permanezca receptiva. En el lado del cliente,
MethodChannel para Flutter
permite
enviar mensajes que corresponden a llamadas a métodos. En el
lado de la plataforma, MethodChannel para Android
y
FlutterMethodChannel para iOS
permiten recibir llamadas a
métodos y devolver un resultado. Estas clases te permiten
desarrollar un plugin de plataforma con muy poco código boilerplate.
Soporte de tipos de datos
#Las API estándar de canales de plataforma y el paquete Pigeon
utilizan un códec de mensajes estándar llamado StandardMessageCodec
que admite una serialización binaria eficiente de
valores simples similares a JSON, como booleanos, números, Strings,
búferes de bytes, Lists y Maps. La serialización y
deserialización de estos valores hacia y desde los mensajes ocurre
automáticamente al enviar y recibir valores.
La siguiente tabla muestra cómo se reciben los valores de Dart en el lado de la plataforma y viceversa:
| Dart | Kotlin |
|---|---|
null | null |
bool | Boolean |
int (<=32 bits) | Int |
int (>32 bits) | Long |
double | Double |
String | String |
Uint8List | ByteArray |
Int32List | IntArray |
Int64List | LongArray |
Float32List | FloatArray |
Float64List | DoubleArray |
List | List |
Map | HashMap |
| Dart | Java |
|---|---|
null | null |
bool | java.lang.Boolean |
int (<=32 bits) | java.lang.Integer |
int (>32 bits) | java.lang.Long |
double | java.lang.Double |
String | java.lang.String |
Uint8List | byte[] |
Int32List | int[] |
Int64List | long[] |
Float32List | float[] |
Float64List | double[] |
List | java.util.ArrayList |
Map | java.util.HashMap |
| Dart | Swift |
|---|---|
null | nil (NSNull cuando está anidado) |
bool | NSNumber(value: Bool) |
int (<=32 bits) | NSNumber(value: Int32) |
int (>32 bits) | NSNumber(value: Int) |
double | NSNumber(value: Double) |
String | String |
Uint8List | FlutterStandardTypedData(bytes: Data) |
Int32List | FlutterStandardTypedData(int32: Data) |
Int64List | FlutterStandardTypedData(int64: Data) |
Float32List | FlutterStandardTypedData(float32: Data) |
Float64List | FlutterStandardTypedData(float64: Data) |
List | Array |
Map | Dictionary |
| Dart | Objective-C |
|---|---|
null | nil (NSNull cuando está anidado) |
bool | NSNumber numberWithBool: |
int (<=32 bits) | NSNumber numberWithInt: |
int (>32 bits) | NSNumber numberWithLong: |
double | NSNumber numberWithDouble: |
String | NSString |
Uint8List | FlutterStandardTypedData typedDataWithBytes: |
Int32List | FlutterStandardTypedData typedDataWithInt32: |
Int64List | FlutterStandardTypedData typedDataWithInt64: |
Float32List |
FlutterStandardTypedData typedDataWithFloat32: |
Float64List |
FlutterStandardTypedData typedDataWithFloat64: |
List | NSArray |
Map | NSDictionary |
| Dart | C++ |
|---|---|
null | EncodableValue() |
bool | EncodableValue(bool) |
int (<=32 bits) | EncodableValue(int32_t) |
int (>32 bits) | EncodableValue(int64_t) |
double | EncodableValue(double) |
String | EncodableValue(std::string) |
Uint8List | EncodableValue(std::vector<uint8_t>) |
Int32List | EncodableValue(std::vector<int32_t>) |
Int64List | EncodableValue(std::vector<int64_t>) |
Float32List | EncodableValue(std::vector<float>) |
Float64List | EncodableValue(std::vector<double>) |
List | EncodableValue(std::vector<EncodableValue>) |
Map |
EncodableValue(std::map<EncodableValue, EncodableValue>) |
| Dart | C (GObject) |
|---|---|
null | FlValue() |
bool | FlValue(bool) |
int | FlValue(int64_t) |
double | FlValue(double) |
String | FlValue(gchar*) |
Uint8List | FlValue(uint8_t*) |
Int32List | FlValue(int32_t*) |
Int64List | FlValue(int64_t*) |
Float32List | FlValue(float*) |
Float64List | FlValue(double*) |
List | FlValue(FlValue) |
Map | FlValue(FlValue, FlValue) |
Llamar a código específico de la plataforma usando canales de plataforma
#El siguiente código demuestra cómo llamar a
una API específica de la plataforma para recuperar y mostrar
el nivel actual de la batería. Utiliza
la API BatteryManager de Android,
la API device.batteryLevel de iOS,
la API GetSystemPowerStatus de Windows
y la API UPower de Linux con un único
mensaje de plataforma, getBatteryLevel().
El ejemplo añade el código específico de la plataforma dentro de la propia aplicación principal. Si deseas reutilizar el código específico de la plataforma en múltiples aplicaciones, el paso de creación del proyecto es ligeramente diferente (consulta desarrollar paquetes), pero el código del canal de plataforma se sigue escribiendo de la misma manera.
Paso 1: Crear un nuevo proyecto de aplicación
#Comienza creando una nueva aplicación:
- En una terminal ejecuta:
flutter create batterylevel
Por defecto, nuestra plantilla admite escribir código de Android utilizando Kotlin,
o código de iOS utilizando Swift. Para utilizar Java o Objective-C,
utiliza las banderas -i y/o -a:
- En una terminal ejecuta:
flutter create -i objc -a java batterylevel
Paso 2: Crear el cliente de plataforma de Flutter
#La clase State de la aplicación mantiene el estado actual de la aplicación.
Extiéndela para mantener el estado actual de la batería.
Primero, construye el canal. Utiliza un MethodChannel con un único
método de plataforma que devuelva el nivel de la batería.
Los lados del cliente y del host de un canal se conectan a través de
un nombre de canal que se pasa en el constructor del canal.
Todos los nombres de canal utilizados en una misma aplicación deben
ser únicos; añade un prefijo único de dominio al nombre del canal,
por ejemplo: samples.flutter.dev/battery.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class _MyHomePageState extends State<MyHomePage> {
static const platform = MethodChannel('samples.flutter.dev/battery');
// Get battery level.
A continuación, invoca un método en el canal de métodos,
especificando el método concreto a llamar mediante
el identificador String getBatteryLevel.
La llamada podría fallar (por ejemplo,
si la plataforma no admite la API de la plataforma,
como al ejecutar en un simulador),
así que envuelve la llamada a invokeMethod en una instrucción try-catch.
Utiliza el resultado devuelto para actualizar el estado de la interfaz de usuario en _batteryLevel
dentro de setState.
// Get battery level.
String _batteryLevel = 'Unknown battery level.';
Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
final result = await platform.invokeMethod<int>('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
setState(() {
_batteryLevel = batteryLevel;
});
}
Finalmente, reemplaza el método build de la plantilla para
que contenga una pequeña interfaz de usuario que muestre el estado de la batería
en una cadena de texto, y un botón para actualizar el valor.
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: _getBatteryLevel,
child: const Text('Get Battery Level'),
),
Text(_batteryLevel),
],
),
),
);
}
Paso 3: Añadir una implementación específica de la plataforma Android
#Comienza abriendo la parte del host de Android de tu aplicación de Flutter en Android Studio:
Iniciar Android Studio
Selecciona la opción de menú File > Open...
-
Navega al directorio que contiene tu aplicación de Flutter y selecciona la carpeta android dentro de él. Haz clic en OK.
-
Abre el archivo
MainActivity.ktubicado en la carpeta kotlin en la vista Project.
Dentro del método configureFlutterEngine(), crea un MethodChannel y llama a
setMethodCallHandler(). Asegúrate de utilizar el mismo nombre de canal que
se utilizó en el lado del cliente de Flutter.
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
private val CHANNEL = "samples.flutter.dev/battery"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
// This method is invoked on the main thread.
// TODO
}
}
}
Añade el código de Android Kotlin que utiliza las API de batería de Android para recuperar el nivel de la batería. Este código es exactamente el mismo que escribirías en una aplicación nativa de Android.
Primero, añade las importaciones necesarias en la parte superior del archivo:
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
A continuación, añade el siguiente método en la clase MainActivity,
debajo del método configureFlutterEngine():
private fun getBatteryLevel(): Int {
val batteryLevel: Int
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
Finalmente, completa el método setMethodCallHandler() añadido anteriormente.
Debes manejar un único método de plataforma, getBatteryLevel(),
así que realiza la comprobación correspondiente en el argumento call.
La implementación de este método de plataforma llama al
código de Android escrito en el paso anterior y devuelve una respuesta tanto para
el caso de éxito como para el de error utilizando el argumento result.
Si se llama a un método desconocido, notifica eso en su lugar.
Elimina el siguiente código:
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
// This method is invoked on the main thread.
// TODO
}
Y reemplázalo con lo siguiente:
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
// This method is invoked on the main thread.
call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
Comienza abriendo la parte del host de Android de tu aplicación de Flutter en Android Studio:
Iniciar Android Studio
Selecciona la opción de menú File > Open...
-
Navega al directorio que contiene tu aplicación de Flutter y selecciona la carpeta android dentro de él. Haz clic en OK.
-
Abre el archivo
MainActivity.javaubicado en la carpeta java en la vista Project.
A continuación, crea un MethodChannel y establece un MethodCallHandler
dentro del método configureFlutterEngine().
Asegúrate de utilizar el mismo nombre de canal que se utilizó en el
lado del cliente de Flutter.
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "samples.flutter.dev/battery";
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
// This method is invoked on the main thread.
// TODO
}
);
}
}
Añade el código de Android Java que utiliza las API de batería de Android para recuperar el nivel de la batería. Este código es exactamente el mismo que escribirías en una aplicación nativa de Android.
Primero, añade las importaciones necesarias en la parte superior del archivo:
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
Luego añade lo siguiente como un nuevo método en la clase activity,
debajo del método configureFlutterEngine():
private int getBatteryLevel() {
int batteryLevel = -1;
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
} else {
Intent intent = new ContextWrapper(getApplicationContext()).
registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
}
return batteryLevel;
}
Finalmente, completa el método setMethodCallHandler() añadido anteriormente.
Debes manejar un único método de plataforma, getBatteryLevel(),
así que realiza la comprobación correspondiente en el argumento call. La implementación de
este método de plataforma llama al código de Android escrito
en el paso anterior y devuelve una respuesta tanto para
el caso de éxito como para el de error utilizando el argumento result.
Si se llama a un método desconocido, notifica eso en su lugar.
Elimina el siguiente código:
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
// This method is invoked on the main thread.
// TODO
}
);
Y reemplázalo con lo siguiente:
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
// This method is invoked on the main thread.
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();
}
}
);
Ahora deberías poder ejecutar la aplicación en Android. Si utilizas el Emulador de Android, establece el nivel de batería en el panel Extended Controls accesible desde el botón ... en la barra de herramientas.
Paso 4: Añadir una implementación específica de la plataforma iOS
#Comienza abriendo la parte del host de iOS de tu aplicación de Flutter en Xcode:
Iniciar Xcode.
Selecciona la opción de menú File > Open....
-
Navega al directorio que contiene tu aplicación de Flutter y selecciona la carpeta ios dentro de él. Haz clic en OK.
Añadir soporte para Swift en la configuración de la plantilla estándar que utiliza Objective-C:
Despliega Runner > Runner en el navegador de proyectos (Project navigator).
-
Abre el archivo
AppDelegate.swiftubicado bajo Runner > Runner en el navegador de proyectos.
Sobrescribe la función application:didFinishLaunchingWithOptions: y crea
un FlutterMethodChannel vinculado al nombre de canal
samples.flutter.dev/battery:
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
let batteryChannel = FlutterMethodChannel(
name: "samples.flutter.dev/battery",
binaryMessenger: engineBridge.applicationRegistrar.messenger()
)
batteryChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
// This method is invoked on the UI thread.
// Handle battery messages.
})
}
}
A continuación, añade el código de iOS Swift que utiliza las API de batería de iOS para recuperar el nivel de la batería. Este código es exactamente el mismo que escribirías en una aplicación nativa de iOS.
Añade lo siguiente como un nuevo método en la parte inferior de AppDelegate.swift:
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))
}
}
Finalmente, completa el método setMethodCallHandler() añadido anteriormente.
Debes manejar un único método de plataforma, getBatteryLevel(),
así que realiza la comprobación correspondiente en el argumento call.
La implementación de este método de plataforma llama
al código de iOS escrito en el paso anterior. Si se llama a un método desconocido,
reporta eso en su lugar.
batteryChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
// This method is invoked on the UI thread.
guard call.method == "getBatteryLevel" else {
result(FlutterMethodNotImplemented)
return
}
self?.receiveBatteryLevel(result: result)
})
Comienza abriendo la parte del host de iOS de la aplicación de Flutter en Xcode:
Iniciar Xcode.
Selecciona la opción de menú File > Open....
-
Navega al directorio que contiene tu aplicación de Flutter y selecciona la carpeta ios dentro de él. Haz clic en OK.
Asegúrate de que el proyecto de Xcode compile sin errores.
-
Abre el archivo
AppDelegate.m, ubicado bajo Runner > Runner en el navegador de proyectos.
Crea un FlutterMethodChannel y añade un manejador dentro del método application didFinishLaunchingWithOptions:.
Asegúrate de utilizar el mismo nombre de canal
que se utilizó en el lado del cliente de Flutter.
#import <Flutter/Flutter.h>
#import "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (void)didInitializeImplicitFlutterEngine:(NSObject<FlutterImplicitEngineBridge>*)engineBridge {
[GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry];
FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
methodChannelWithName:@"samples.flutter.dev/battery"
binaryMessenger:engineBridge.applicationRegistrar.messenger];
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
// This method is invoked on the UI thread.
// TODO
}];
}
@end
A continuación, añade el código de iOS Objective-C que utiliza las API de batería de iOS para recuperar el nivel de la batería. Este código es exactamente el mismo que escribirías en una aplicación nativa de iOS.
Añade el siguiente método en la clase AppDelegate, justo antes de @end:
- (int)getBatteryLevel {
UIDevice* device = UIDevice.currentDevice;
device.batteryMonitoringEnabled = YES;
if (device.batteryState == UIDeviceBatteryStateUnknown) {
return -1;
} else {
return (int)(device.batteryLevel * 100);
}
}
Finalmente, completa el método setMethodCallHandler() añadido anteriormente.
Debes manejar un único método de plataforma, getBatteryLevel(),
así que realiza la comprobación correspondiente en el argumento call. La implementación de
este método de plataforma llama al código de iOS escrito en el paso anterior
y devuelve una respuesta tanto para el caso de éxito como para el de error utilizando
el argumento result. Si se llama a un método desconocido, reporta eso en su lugar.
__weak typeof(self) weakSelf = self;
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
// This method is invoked on the UI thread.
if ([@"getBatteryLevel" isEqualToString:call.method]) {
int batteryLevel = [weakSelf getBatteryLevel];
if (batteryLevel == -1) {
result([FlutterError errorWithCode:@"UNAVAILABLE"
message:@"Battery level not available."
details:nil]);
} else {
result(@(batteryLevel));
}
} else {
result(FlutterMethodNotImplemented);
}
}];
Ahora deberías poder ejecutar la aplicación en iOS. Si utilizas el Simulador de iOS, ten en cuenta que este no admite las API de batería, y la aplicación mostrará 'Battery level not available'.
Paso 5: Añadir una implementación específica de la plataforma Windows
#Comienza abriendo la parte del host de Windows de tu aplicación de Flutter en Visual Studio:
-
Ejecuta
flutter build windowsen el directorio de tu proyecto una vez para generar el archivo de solución de Visual Studio. Iniciar Visual Studio.
Selecciona Open a project or solution.
-
Navega al directorio que contiene tu aplicación de Flutter, luego a la carpeta build, luego a la carpeta windows, y finalmente selecciona el archivo
batterylevel.sln. Haz clic en Open.
Añadir la implementación en C++ del método del canal de plataforma:
Despliega batterylevel > Source Files en el Solution Explorer.
Abre el archivo
flutter_window.cpp.
Primero, añade los includes necesarios en la parte superior del archivo, justo
después de #include "flutter_window.h":
#include <flutter/event_channel.h>
#include <flutter/event_sink.h>
#include <flutter/event_stream_handler_functions.h>
#include <flutter/method_channel.h>
#include <flutter/standard_method_codec.h>
#include <windows.h>
#include <memory>
Edita el método FlutterWindow::OnCreate y crea
un flutter::MethodChannel vinculado al nombre de canal
samples.flutter.dev/battery:
bool FlutterWindow::OnCreate() {
// ...
RegisterPlugins(flutter_controller_->engine());
flutter::MethodChannel<> channel(
flutter_controller_->engine()->messenger(), "samples.flutter.dev/battery",
&flutter::StandardMethodCodec::GetInstance());
channel.SetMethodCallHandler(
[](const flutter::MethodCall<>& call,
std::unique_ptr<flutter::MethodResult<>> result) {
// TODO
});
SetChildContent(flutter_controller_->view()->GetNativeWindow());
return true;
}
A continuación, añade el código C++ que utiliza las API de batería de Windows para recuperar el nivel de la batería. Este código es exactamente el mismo que escribirías en una aplicación nativa de Windows.
Añade lo siguiente como una nueva función en la parte superior de
flutter_window.cpp justo después de la sección #include:
static int GetBatteryLevel() {
SYSTEM_POWER_STATUS status;
if (GetSystemPowerStatus(&status) == 0 || status.BatteryLifePercent == 255) {
return -1;
}
return status.BatteryLifePercent;
}
Finalmente, completa el método setMethodCallHandler() añadido anteriormente.
Debes manejar un único método de plataforma, getBatteryLevel(),
así que realiza la comprobación correspondiente en el argumento call.
La implementación de este método de plataforma llama
al código de Windows escrito en el paso anterior. Si se llama a un método desconocido,
notifica eso en su lugar.
Elimina el siguiente código:
channel.SetMethodCallHandler(
[](const flutter::MethodCall<>& call,
std::unique_ptr<flutter::MethodResult<>> result) {
// TODO
});
Y reemplázalo con lo siguiente:
channel.SetMethodCallHandler(
[](const flutter::MethodCall<>& call,
std::unique_ptr<flutter::MethodResult<>> result) {
if (call.method_name() == "getBatteryLevel") {
int battery_level = GetBatteryLevel();
if (battery_level != -1) {
result->Success(battery_level);
} else {
result->Error("UNAVAILABLE", "Battery level not available.");
}
} else {
result->NotImplemented();
}
});
Ahora deberías poder ejecutar la aplicación en Windows. Si tu dispositivo no tiene batería, mostrará 'Battery level not available'.
Paso 6: Añadir una implementación específica de la plataforma macOS
#Comienza abriendo la parte del host de macOS de tu aplicación de Flutter en Xcode:
Iniciar Xcode.
Selecciona la opción de menú File > Open....
-
Navega al directorio que contiene tu aplicación de Flutter y selecciona la carpeta macos dentro de él. Haz clic en OK.
Añadir la implementación en Swift del método del canal de plataforma:
Despliega Runner > Runner en el navegador de proyectos (Project navigator).
-
Abre el archivo
MainFlutterWindow.swiftubicado bajo Runner > Runner en el navegador de proyectos.
Primero, añade la importación necesaria en la parte superior del archivo, justo después de
import FlutterMacOS:
import IOKit.ps
Crea un FlutterMethodChannel vinculado al nombre de canal
samples.flutter.dev/battery en el método awakeFromNib:
override func awakeFromNib() {
// ...
self.setFrame(windowFrame, display: true)
let batteryChannel = FlutterMethodChannel(
name: "samples.flutter.dev/battery",
binaryMessenger: flutterViewController.engine.binaryMessenger)
batteryChannel.setMethodCallHandler { (call, result) in
// This method is invoked on the UI thread.
// Handle battery messages.
}
RegisterGeneratedPlugins(registry: flutterViewController)
super.awakeFromNib()
}
}
A continuación, añade el código de macOS Swift que utiliza las API de batería de IOKit para recuperar el nivel de la batería. Este código es exactamente el mismo que escribirías en una aplicación nativa de macOS.
Añade lo siguiente como un nuevo método en la parte inferior de MainFlutterWindow.swift:
private func getBatteryLevel() -> Int? {
let info = IOPSCopyPowerSourcesInfo().takeRetainedValue()
let sources: Array<CFTypeRef> = IOPSCopyPowerSourcesList(info).takeRetainedValue() as Array
if let source = sources.first {
let description =
IOPSGetPowerSourceDescription(info, source).takeUnretainedValue() as! [String: AnyObject]
if let level = description[kIOPSCurrentCapacityKey] as? Int {
return level
}
}
return nil
}
Finalmente, completa el método setMethodCallHandler añadido anteriormente.
Debes manejar un único método de plataforma, getBatteryLevel(),
así que realiza la comprobación correspondiente en el argumento call.
La implementación de este método de plataforma llama
al código de macOS escrito en el paso anterior. Si se llama a un método desconocido,
notifica eso en su lugar.
batteryChannel.setMethodCallHandler { (call, result) in
switch call.method {
case "getBatteryLevel":
guard let level = getBatteryLevel() else {
result(
FlutterError(
code: "UNAVAILABLE",
message: "Battery level not available",
details: nil))
return
}
result(level)
default:
result(FlutterMethodNotImplemented)
}
}
Ahora deberías poder ejecutar la aplicación en macOS. Si tu dispositivo no tiene batería, mostrará 'Battery level not available'.
Paso 7: Añadir una implementación específica de la plataforma Linux
#Para este ejemplo, necesitas instalar las cabeceras de desarrollo de upower.
Es probable que estén disponibles en tu distribución, por ejemplo con:
sudo apt install libupower-glib-dev
Comienza abriendo la parte del host de Linux de tu aplicación de Flutter en el editor de tu elección. Las instrucciones a continuación son para Visual Studio Code con las extensiones "C/C++" y "CMake" instaladas, pero se pueden adaptar para otros IDE.
Inicia Visual Studio Code.
Abre el directorio linux dentro de tu proyecto.
-
Elige Yes en el mensaje que pregunta:
Would you like to configure project "linux"?. Esto habilita el autocompletado de C++. Abre el archivo
runner/my_application.cc.
Primero, añade los includes necesarios en la parte superior del archivo, justo
después de #include <flutter_linux/flutter_linux.h>:
#include <math.h>
#include <upower.h>
Añade un FlMethodChannel a la estructura _MyApplication:
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
FlMethodChannel* battery_channel;
};
Asegúrate de limpiarlo en my_application_dispose:
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
g_clear_object(&self->battery_channel);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
Edita el método my_application_activate e inicializa
battery_channel utilizando el nombre de canal
samples.flutter.dev/battery, justo después de la llamada a
fl_register_plugins:
static void my_application_activate(GApplication* application) {
// ...
fl_register_plugins(FL_PLUGIN_REGISTRY(self->view));
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
self->battery_channel = fl_method_channel_new(
fl_engine_get_binary_messenger(fl_view_get_engine(view)),
"samples.flutter.dev/battery", FL_METHOD_CODEC(codec));
fl_method_channel_set_method_call_handler(
self->battery_channel, battery_method_call_handler, self, nullptr);
gtk_widget_grab_focus(GTK_WIDGET(self->view));
}
A continuación, añade el código C que utiliza las API de batería de Linux para recuperar el nivel de la batería. Este código es exactamente el mismo que escribirías en una aplicación nativa de Linux.
Añade lo siguiente como una nueva función en la parte superior de
my_application.cc justo después de la línea G_DEFINE_TYPE:
static FlMethodResponse* get_battery_level() {
// Find the first available battery and report that.
g_autoptr(UpClient) up_client = up_client_new();
g_autoptr(GPtrArray) devices = up_client_get_devices2(up_client);
if (devices->len == 0) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
"UNAVAILABLE", "Device does not have a battery.", nullptr));
}
UpDevice* device = UP_DEVICE(g_ptr_array_index(devices, 0));
double percentage = 0;
g_object_get(device, "percentage", &percentage, nullptr);
g_autoptr(FlValue) result =
fl_value_new_int(static_cast<int64_t>(round(percentage)));
return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
}
Finalmente, añade la función battery_method_call_handler referenciada
en la llamada anterior a fl_method_channel_set_method_call_handler.
Debes manejar un único método de plataforma, getBatteryLevel,
así que realiza la comprobación correspondiente en el argumento method_call.
La implementación de esta función llama
al código de Linux escrito en el paso anterior. Si se llama a un método desconocido,
notifica eso en su lugar.
Añade el siguiente código después de la función get_battery_level:
static void battery_method_call_handler(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data) {
g_autoptr(FlMethodResponse) response = nullptr;
if (strcmp(fl_method_call_get_name(method_call), "getBatteryLevel") == 0) {
response = get_battery_level();
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
g_autoptr(GError) error = nullptr;
if (!fl_method_call_respond(method_call, response, &error)) {
g_warning("Failed to send response: %s", error->message);
}
}
Ahora deberías poder ejecutar la aplicación en Linux. Si tu dispositivo no tiene batería, mostrará 'Battery level not available'.
Llamar a código específico de la plataforma usando el paquete Pigeon
#Puedes utilizar el paquete Pigeon como
una alternativa a las API de canales de plataforma de Flutter
para generar código que envíe mensajes de manera
estructurada y con tipado seguro. El flujo de trabajo de Pigeon
es el siguiente:
-
La aplicación de Flutter envía mensajes estructurados con tipado seguro a su host, la parte de la aplicación que no es Dart, a través de un canal de plataforma.
-
El host escucha en el canal de plataforma y recibe el mensaje. Luego realiza llamadas a cualquier número de API específicas de la plataforma utilizando el lenguaje de programación nativo y devuelve una respuesta al cliente, la parte de Flutter de la aplicación.
El uso de este paquete elimina la necesidad de hacer coincidir cadenas de texto entre el host y el cliente para los nombres y tipos de datos de los mensajes. Admite clases anidadas, agrupación de mensajes en API, generación de código contenedor (wrapper) asíncrono y envío de mensajes en cualquier dirección. El código generado es legible y garantiza que no haya conflictos entre múltiples clientes de diferentes versiones.
Con Pigeon, el protocolo de mensajería se define en un subconjunto de Dart que luego genera código de mensajería para Android, iOS, macOS o Windows. Por ejemplo:
import 'package:pigeon/pigeon.dart';
class SearchRequest {
final String query;
SearchRequest({required this.query});
}
class SearchReply {
final String result;
SearchReply({required this.result});
}
@HostApi()
abstract class Api {
@async
SearchReply search(SearchRequest request);
}
import 'generated_pigeon.dart';
Future<void> onClick() async {
SearchRequest request = SearchRequest(query: 'test');
Api api = SomeApi();
SearchReply reply = await api.search(request);
print('reply: ${reply.result}');
}
Puedes encontrar un ejemplo completo y más información
en la página de pigeon en pub.dev.
Canales y subprocesamiento de la plataforma
#Al invocar canales en el lado de la plataforma destinados a Flutter,
invócalos en el hilo principal de la plataforma.
Al invocar canales en Flutter destinados al lado de la plataforma,
invócalos desde cualquier Isolate que sea el Isolate
raíz, o que esté registrado como un Isolate en segundo plano.
Los manejadores para el lado de la plataforma pueden ejecutarse en el hilo principal de la plataforma
o pueden ejecutarse en un hilo en segundo plano si se utiliza una Task Queue.
Puedes invocar los manejadores del lado de la plataforma de forma asíncrona
y en cualquier hilo.
Usar plugins y canales desde un isolate en segundo plano
#Los plugins y los canales pueden ser utilizados por cualquier Isolate, pero ese Isolate tiene que ser
un Isolate raíz (el creado por Flutter) o estar registrado como un Isolate
en segundo plano para un Isolate raíz.
El siguiente ejemplo muestra cómo registrar un Isolate en segundo plano para
utilizar un plugin desde un Isolate en segundo plano.
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
void _isolateMain(RootIsolateToken rootIsolateToken) async {
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
print(sharedPreferences.getBool('isDebug'));
}
void main() {
RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
Isolate.spawn(_isolateMain, rootIsolateToken);
}
Ejecutar manejadores de canales en un hilo en segundo plano (Android)
#Para que el manejador del lado de la plataforma de un canal se ejecute en un hilo en segundo plano en una aplicación de Android, debes utilizar la API Task Queue.
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
val taskQueue =
flutterPluginBinding.binaryMessenger.makeBackgroundTaskQueue()
channel = MethodChannel(flutterPluginBinding.binaryMessenger,
"com.example.foo",
StandardMethodCodec.INSTANCE,
taskQueue)
channel.setMethodCallHandler(this)
}
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
BinaryMessenger messenger = binding.getBinaryMessenger();
BinaryMessenger.TaskQueue taskQueue =
messenger.makeBackgroundTaskQueue();
channel =
new MethodChannel(
messenger,
"com.example.foo",
StandardMethodCodec.INSTANCE,
taskQueue);
channel.setMethodCallHandler(this);
}
Ejecutar manejadores de canales en un hilo en segundo plano (iOS)
#Para que el manejador del lado de la plataforma de un canal se ejecute en un hilo en segundo plano en una aplicación de iOS, debes utilizar la API Task Queue.
public static func register(with registrar: FlutterPluginRegistrar) {
let taskQueue = registrar.messenger().makeBackgroundTaskQueue?()
let channel = FlutterMethodChannel(name: "com.example.foo",
binaryMessenger: registrar.messenger(),
codec: FlutterStandardMethodCodec.sharedInstance(),
taskQueue: taskQueue)
let instance = MyPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
NSObject<FlutterTaskQueue>* taskQueue =
[[registrar messenger] makeBackgroundTaskQueue];
FlutterMethodChannel* channel =
[FlutterMethodChannel methodChannelWithName:@"com.example.foo"
binaryMessenger:[registrar messenger]
codec:[FlutterStandardMethodCodec sharedInstance]
taskQueue:taskQueue];
MyPlugin* instance = [[MyPlugin alloc] init];
[registrar addMethodCallDelegate:instance channel:channel];
}
Saltar al hilo de interfaz de usuario (Android)
#Para cumplir con el requisito de hilo de interfaz de usuario de los canales,
es posible que debas pasar de un hilo en segundo plano
al hilo de interfaz de usuario de Android para ejecutar un método de canal.
En Android, puedes lograr esto enviando mediante post() un
Runnable al Looper del hilo de interfaz de usuario de Android,
lo que hace que el Runnable se ejecute en el hilo
principal en la siguiente oportunidad.
Handler(Looper.getMainLooper()).post {
// Call the desired channel message here.
}
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// Call the desired channel message here.
}
});
Saltar al hilo principal (iOS)
#Para cumplir con el requisito de hilo principal del canal, es posible que debas pasar de un hilo en segundo plano al hilo principal de iOS para ejecutar un método de canal. Puedes lograr esto en iOS ejecutando un bloque en la cola de despacho principal (main dispatch queue):
dispatch_async(dispatch_get_main_queue(), ^{
// Call the desired channel message here.
});
DispatchQueue.main.async {
// Call the desired channel message here.
}
Información complementaria
#Canales y códecs comunes
#La siguiente es una lista de algunas API de canales de plataforma comunes que puedes utilizar para escribir código específico de la plataforma:
-
MethodChannelpara Flutter: Un canal con nombre que puedes utilizar para comunicarte con plugins de la plataforma utilizando llamadas a métodos asíncronos. Por defecto, este canal utiliza el códecStandardMessageCodec. Este canal no tiene tipado seguro, lo que significa que llamar y recibir mensajes depende de que el host y el cliente declaren los mismos argumentos y tipos de datos para que los mensajes funcionen. -
BasicMessageChannelpara Flutter: Un canal con nombre que admite el paso de mensajes básicos y asíncronos, utilizando un códec de mensajes compatible. No tiene tipado seguro. -
API de Engine Embedder para plataformas: Estas API específicas de la plataforma contienen API de canales específicas de la plataforma.
Puedes crear tu propio códec o utilizar uno existente. La siguiente es una lista de algunos códecs existentes que puedes utilizar con código específico de la plataforma:
-
StandardMessageCodec: Un códec de mensajes de uso común que codifica y decodifica una amplia gama de tipos de datos en un formato binario independiente de la plataforma para su transmisión a través de canales de plataforma. La serialización y deserialización de valores hacia y desde los mensajes ocurre automáticamente cuando envías y recibes valores. Para obtener una lista de los tipos de datos admitidos, consulta Soporte de tipos de datos de canales de plataforma. -
BinaryCodec: Un códec de mensajes que transmite datos binarios en bruto entre el lado de Dart de tu aplicación de Flutter y el lado de la plataforma nativa. No realiza ninguna codificación o decodificación de estructuras de datos de nivel superior. -
StringCodec: Un códec de mensajes que codifica y decodifica cadenas de texto, utilizando codificación UTF-8. -
JSONMessageCodec: Un códec de mensajes que codifica y decodifica datos con formato JSON, utilizando codificación UTF-8. -
FirestoreMessageCodec: Un códec de mensajes que maneja el intercambio de mensajes enviados a través del canal de plataforma entre tu aplicación de Flutter y los SDK nativos de Firebase Firestore (en Android e iOS).
Separar el código específico de la plataforma del código de la interfaz de usuario
#Si esperas utilizar tu código específico de la plataforma en múltiples aplicaciones de Flutter, podrías considerar separar el código en un plugin de plataforma ubicado en un directorio fuera de tu aplicación principal. Consulta desarrollar paquetes para obtener más detalles.
Publicar código específico de la plataforma como un paquete
#Para compartir tu código específico de la plataforma con otros desarrolladores en el ecosistema de Flutter, consulta publicar paquetes.
A menos que se indique lo contrario, la documentación de este sitio refleja Flutter 3.44.0. Página actualizada por última vez el 2026-05-05. Ver código fuente oreportar un problema.