Esta guía describe como escribir código personalizado, específico de plataforma. Algunas funcionalidades específicas de plataforma están disponibles a través de los paquetes existentes; por favor mire usando paquetes.
Flutter usa un sistema flexible que permite llamar APIs específicas de plataforma ya esté disponible en código Java o Kotlin en Android, o en código Objective-C o Swift en iOS.
El soporte de APIs específicas de plataforma de Flutter, no está relacionado con la generación de código, sino más bien con un estilo flexible de paso de mensajes:
- La parte Flutter de tu app envía mensajes a su host, la parte iOS o Android de tu app, usando platform channel.
- El host escucha el platform channel, y recibe el mensaje. Esto entonces permite llamar a cualquier número de APIs específicas de plataforma—usando el lenguaje de programación nativo—y devuelve una respuesta al cliente, la parte Flutter de tu app.
Visión general de la arquitectura: platform channels
Los mensajes son pasados entre el cliente (UI) y el host (plataforma) usando platform channels.
Los mensajes y las respuestas son pasados de forma asíncrona, para asegurar que el interfaz de usuario permanece responsivo.
En el lado del cliente, la (API) MethodChannel
permite enviar mensajes que corresponden con llamadas a métodos. En el lado de la plataforma, la (API) MethodChannel
en Android y la (API) FlutterMethodChannel
en iOS permiten recibir llamadas a métodos y devolver un resultado. Estas clases te permiten desarrollar un plugin de plataforma con un código ‘boilerplate’ verdaderamente pequeño.
Tipos de datos y codecs soportados por Platform channel
Eñ platform channels estándar usa un codec de mensaje estándar que soporta serialización binaria eficiente de valores similares a JSON simples, como booleans, numbers, Strings, byte buffers, y List y Maps de estos (mira StandardMessageCodec
) para detalles. La serialización y deserialización de estos valores hacia y desde los mensajes ocurre automáticamente cuando envías y recibes valores.
La siguiente tabla muestra como son recibidos los valores Dart en la plataforma y viceversa:
Dart | Android | iOS |
---|---|---|
null | null | nil (NSNull cuando están anidados) |
bool | java.lang.Boolean | NSNumber numberWithBool: |
int | java.lang.Integer | NSNumber numberWithInt: |
int, si 32 bits no son suficientes | java.lang.Long | NSNumber numberWithLong: |
double | java.lang.Double | NSNumber numberWithDouble: |
String | java.lang.String | NSString |
Uint8List | byte[] | FlutterStandardTypedData typedDataWithBytes: |
Int32List | int[] | FlutterStandardTypedData typedDataWithInt32: |
Int64List | long[] | FlutterStandardTypedData typedDataWithInt64: |
Float64List | double[] | FlutterStandardTypedData typedDataWithFloat64: |
List | java.util.ArrayList | NSArray |
Map | java.util.HashMap | NSDictionary |
Ejemplo: Llamando código especifico de plataforma iOS y Android usando platform channels
Lo siguiente demuestra como llamar una API especifica de plataforma para obtener y mostrar el nivel de batería actual. Usa la API BatteryManager
de Android, y la API device.batteryLevel
de iOS, vía un único mensaje de plataforma, getBatteryLevel
.
El ejemplo añade el código especifico de plataforma dentro del método main de la app. Si quieres reutilizar el código específico de plataforma para múltiples aplicaciones, el paso de creación del proyecto es ligeramente diferente (mira desarrollando paquetes), pero el código del platform channel se escribe de la misma manera.
Nota: El código fuente ejecutable completo para este ejemplo, está disponible en /examples/platform_channel/
para Android con Java y iOS con Objective-C. Para iOS con Swift, mira /examples/platform_channel_swift/
.
Step 1: Crea un nuevo proyecto de app
Empieza creando una nueva app:
- En un terminal ejecuta:
flutter create batterylevel
Por defecto nuestra plantilla soporta escribir código Android usando Java , o código iOS usando Objective-C. Para usar Kotlin o Swift, usa el flag -i
y/o -a
:
- En un terminal ejecuta:
flutter create -i swift -a kotlin batterylevel
Step 2: Crea un platform client de Flutter
La clase State
de la app conserva el estado actual de la app. Necesitamos ampliar esto para conservar el estado actual de la batería.
Primero, construimos el channel. Usamos un MethodChannel
con un único método de plataforma que devuelve el nivel de batería.
Los lados cliente y host de un channel son conectados a través de un channel name pasado en el constructor del channel. Todos los channel names usados en una app deben ser únicos; recomendamos prefijar el channel name con un ‘prefijo de dominio’ único’, ej. samples.flutter.io/battery
.content_copy
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
...
class _MyHomePageState extends State<MyHomePage> {
static const platform = const MethodChannel('samples.flutter.io/battery');
// Obtiene el nivel de batería.
}
A continuación, invocamos un método en el method channel, especificando el método concreto a llamar vía un String identificador getBatteryLevel
. La llamada puede fallar—por ejemplo si la plataforma no soporta la API de plataforma (si estamos ejecutando en un simulador), entonces envolveremos la llamada a invokeMethod
en una declaración try-catch.
Usamos el resultado devuelto para actualizar el estado de el interfaz de usuario en _batteryLevel
dentro de setState
.content_copy
// Get battery level.
String _batteryLevel = 'Unknown battery level.';
Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
setState(() {
_batteryLevel = batteryLevel;
});
}
Finalmente, reemplazamos el método build
de la plantilla para contener una pequeña interfaz de usuario que muestra el estado de la batería en un string, y un botón para refrescar el valor.content_copy
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
child: Text('Get Battery Level'),
onPressed: _getBatteryLevel,
),
Text(_batteryLevel),
],
),
),
);
}
Paso 3a: Añade la implementación especifica de plataforma Android usando Java
Nota: El siguiente paso usa Java. Si prefieres Kotlin, salta al paso 3b.
Empieza abriendo la parte host de Android de tu app Flutter en Android Studio:
- Inicia Android Studio
- Selecciona el menú ‘File > Open…’
- Navega al directorio que contiene tu app Flutter, y selecciona la carpeta
android
dentro de él. Haz clic en OK. - Abre el fichero
MainActivity.java
localizado en la carpetajava
en la vista de Proyecto.
A continuación, crea un MethodChannel
y establece un MethodCallHandler
dentro del método onCreate
. Asegúrate de usar el mismo channel name que fue usado en el lado del cliente en Flutter.content_copy
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "samples.flutter.io/battery";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
// TODO
}
});
}
}
A continuación, añadimos el código actual Android en Java, que usa la API Android battery para obtener el nivel de la batería. Este código es exactamente el mismo que habrías escrito en una app nativa Android.
Primero, añade los imports necesarios al principio del fichero:content_copy
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;
Entonces añade lo siguiente como un método en la clase activity, debajo del método onCreate
:content_copy
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, completamos el método onMethodCall
que añadimos anteriormente. Necesitas manejar un único método de plataforma, getBatteryLevel
, vamos a probar esto en el argumento call
. La implementación de este método de plataforma simplemente llama al código Android escrito en el paso anterior, y pasa de vuelta una respuesta para ambos casos, éxito y error, usando el argumento response
. En cambio, si se llama un método desconocido, reportaremos esto. Reemplaza:content_copy
public void onMethodCall(MethodCall call, Result result) {
// TODO
}
con:content_copy
@Override
public void onMethodCall(MethodCall call, Result 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();
}
}
Deberías ahora poder ejecutar la app en Android. Si estas usando el emulador de Android, puedes ajustar el nivel de batería en el panel Extended Controls accesible desde el botón ...
en la barra de herramientas.
Paso 3b: Añadir una implementación espéfica de plataforma Android usando Kotlin
Nota: Los siguientes pasos son similares al paso 3a, solo usaremos Kotlin en lugar de Java.
Este paso asume que has creado tu proyecto en el paso 1 usando la opción -a kotlin
.
Empieza abriendo la parte host Android de tu app Flutter en Android Studio:
- Inicia Android Studio
- Selecciona el menú ‘File > Open…’
- Navega hasta el directorio que contiene tu app Flutter, y selecciona la carpeta
android
dentro de él. Haz Clic en OK. - Abre el fichero
MainActivity.kt
localizado en la carpetakotlin
en la Project view. (Nota: Si estas editando usando Android Studio 2.3, nota que la carpeta ‘kotlin’ se muestra como si se llamara ‘java’.)
A continuación, dentro del método onCreate
, crea un MethodChannel
y llama a setMethodCallHandler
. Asegúrate que usas el mismo channel name que fue usado la parte cliente en Flutter.content_copy
import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugin.common.MethodChannel
class MainActivity() : FlutterActivity() {
private val CHANNEL = "samples.flutter.io/battery"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
// TODO
}
}
}
A continuación, añade el código actual Kotlin de Android que usa la API Android battery para obtener el nivel de batería. Este código es exactamente el mismo que escribirías en una app Android nativa.
Primero, añade los imports necesarios al principio del fichero:content_copy
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 lo siguiente como un nuevo método en la clase MainActivity
, debajo del método onCreate
:content_copy
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 onMethodCall
que añadimos anteriormente. Necesitamos manejar un único método de plataforma, getBatteryLevel
, entonces probamos esto en el argumento call
. La implementación de ese método de plataforma simplemente llama al código Android escrito en el paso anterior, y pasamos de vuelta una respuesta para ambos casos, éxito y error, usando el argumento response
. En cambio, si se llama un método desconocido, reporta esto. Reemplaza:content_copy
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
// TODO
}
with:content_copy
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { 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()
}
}
Deberías poder ejecutar tu app en Android. Si estas usando el emulador de Android, puedes ajustar el nivel de batería en el panel Extended Controls accesible desde el botón ...
en la barra de herramientas.
Paso 4a: Añade la implementación específica de la plataforma iOS usando Objective-C
Nota: Los siguientes pasos usan Objective-C. Si prefieres Swift, salta al paso 4b.
Empieza abriendo la parte iOS de tu app Flutter en Xcode:
- Inicia Xcode
- Selecciona el menú ‘File > Open…’
- Navega hasta el directorio que contiene tu app Flutter, y selecciona la carpeta
ios
dentro de él. Haz Clic en OK. - Asegúrate que el proyecto Xcode compila sin errores.
- Abre el fichero
AppDelegate.m
situado bajo Runner > Runner en el navegador Project.
A continuación, crea un FlutterMethodChannel
y añade un manejador dentro del método application didFinishLaunchingWithOptions:
. Asegúrate de usar el mismo channel name que fue usado en la parte del cliente en Flutter.content_copy
#import <Flutter/Flutter.h>
#import "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
methodChannelWithName:@"samples.flutter.io/battery"
binaryMessenger:controller];
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
// TODO
}];
[GeneratedPluginRegistrant registerWithRegistry:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
A continuación, añade el código actual ObjectiveC en iOS que usa la API iOS battery para obtener el nivel de batería. Este código es exactamente el mismo que habrías escrito en una app nativa iOS.
Añade lo siguiente como un nuevo método en la clase AppDelegate
, justo antes de @end
:content_copy
- (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. Necesitas manejar un único método de plataforma, getBatteryLevel
, entonces probamos esto en el argumento call
. La implementación de este método de plataforma simplemente llama al código iOS escrito en el paso anterior, y pasa de vuelta una respuesta para ambos casos, éxito y error, usando el argumento result
. En cambio, si se llama un método desconocido, reporta esto. Reemplaza:content_copy
__weak typeof(self) weakSelf = self
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if ([@"getBatteryLevel" isEqualToString:call.method]) {
int batteryLevel = [weakSelf getBatteryLevel];
if (batteryLevel == -1) {
result([FlutterError errorWithCode:@"UNAVAILABLE"
message:@"Battery info unavailable"
details:nil]);
} else {
result(@(batteryLevel));
}
} else {
result(FlutterMethodNotImplemented);
}
}];
Ahora deberías poder ejecutar la app en iOS. Si estas usando el simulador de iOS, nota que este no soporta la API battery, y la app mostrara ‘battery info unavailable’.
Paso 4b: Añade la implementación específica de la plataforma iOS usando Swift
Nota: Los siguientes pasos son similares al paso 4a, solo usando Swift en lugar de Objective-C.
Este paso asume que has creado tu proyecto en el paso 1. usando la opción -i swift
.
Empieza abriendo la parte iOS de tu app Flutter en Xcode:
- Inicia Xcode
- Selecciona el menú ‘File > Open…’
- Navega hasta el directorio que contiene tu app Flutter, y selecciona la carpeta
ios
dentro de él. Haz Clic en OK.
A continuación, añade soporte para Swift en la configuración de la plantilla standard que usa Objective-C:
- Expande Runner > Runner en Project navigator.
- Abre el fichero
AppDelegate.swift
localizado bajo Runner > Runner en Project navigator.
A continuación, sobrescribe la función application:didFinishLaunchingWithOptions:
y crea un FlutterMethodChannel
atado al channel name samples.flutter.io/battery
:content_copy
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel(name: "samples.flutter.io/battery",
batteryChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: FlutterResult) -> Void in
// Maneja mensajes sobre la batería.
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
A continuación, añadimos el código actual Swift en iOS que usa la API iOS battery para obtener el nivel de batería. Este código es exactamente el mismo que habrías escrito en una app nativa iOS.
Añade lo siguiente como un nuevo método al final de AppDelegate.swift
:content_copy
private func receiveBatteryLevel(result: FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
if device.batteryState == UIDeviceBatteryState.unknown {
result(FlutterError(code: "UNAVAILABLE",
message: "Battery info unavailable",
details: nil))
} else {
result(Int(device.batteryLevel * 100))
}
}
Finalmente, completa el método setMethodCallHandler
añadido anteriormente. Necesitas manejar un único método de plataforma, getBatteryLevel
, entonces probamos esto en el argumento call
. La implementación de este método de plataforma simplemente llama al código iOS escrito en el paso anterior.Si se llama un método desconocido, reportaremos esto. Reemplaza:content_copy
batteryChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
guard call.method == "getBatteryLevel" else {
result(FlutterMethodNotImplemented)
return
}
self?.receiveBatteryLevel(result: result)
})
Ahora deberiás poder ejecutar la app en iOS. Si estas usando el simulador de iOS, nota que este no soporta la API battery, y la app muestra ‘battery info unavailable’.
Separa código específico de la plataforma del código de UI
Si esperas usar tu código específico de plataforma en múltiples aplicaciones Flutter, puede ser útil separar el código en un plugin de plataforma situado en un directorio fuera de tu aplicación. Mira deserrollando paquetes para los detalles.
Publica código específico de plataforma como un paquete
Si deseas compartir tu paquete específico de plataforma con otros desarrolladores en el ecosistema Flutter, por favor mira publishing packages para los detalles.
Custom channels y codecs
Además de MethodChannel
mencionado anteriormente, también puedes usar el básico BasicMessageChannel
, que soporta paso de mensajes asíncronos básicos, usando un codec de mensajes personalizado. Además, puedes usar las clases especializadas BinaryCodec
, StringCodec
, y JSONMessageCodec
, o crear tu propio codec.
Fuente: esflutter