Saltar al contenido principal

Soporte para las nuevas API de plugins de Android

Cómo actualizar un plugin que utiliza las API antiguas para que sea compatible con las nuevas API.

Si no escribes ni mantienes un plugin de Flutter para Android, puedes saltarte esta página.

A partir del lanzamiento 1.12, nuevas API de plugin están disponibles para la plataforma Android. Las API antiguas basadas en PluginRegistry.Registrar no se marcarán como obsoletas inmediatamente, pero te recomendamos migrar a las nuevas API basadas en FlutterPlugin.

La nueva API tiene la ventaja de proporcionar un conjunto más limpio de accesores para componentes que dependen del ciclo de vida en comparación con las API antiguas. Por ejemplo, PluginRegistry.Registrar.activity() podría devolver null si Flutter no está vinculado a ninguna actividad.

En otras palabras, los plugins que usan la API antigua podrían producir comportamientos indefinidos al incrustar Flutter en una aplicación de Android. La mayoría de los plugins de Flutter proporcionados por el equipo de flutter.dev ya han sido migrados. (¡Aprende cómo convertirte en un editor verificado en pub.dev!). Para ver un ejemplo de un plugin que usa las nuevas API, consulta el paquete battery_plus.

Pasos de actualización

#

Las siguientes instrucciones detallan los pasos para dar soporte a la nueva API:

  1. Actualiza la clase principal del plugin (*Plugin.java) para implementar la interfaz FlutterPlugin. Para plugins más complejos, puedes separar FlutterPlugin y MethodCallHandler en dos clases. Consulta la siguiente sección, Plugin básico, para obtener más detalles sobre el acceso a los recursos de la aplicación con la última versión (v2) de la integración (embedding).

    Además, ten en cuenta que el plugin aún debe contener el método estático registerWith() method para seguir siendo compatible con las aplicaciones que no utilizan la integración v2 de Android. (Consulta Actualización de proyectos de Android anteriores a 1.12 para más detalles). Lo más fácil de hacer (si es posible) es mover la lógica de registerWith() a un método privado que puedan llamar tanto registerWith() y onAttachedToEngine(). Se llamará a registerWith() o a onAttachedToEngine(), no a ambos.

    Además, debes documentar todos los miembros públicos no sobrescritos dentro del plugin. En un escenario de add-to-app, estas clases son accesibles para un desarrollador y requieren documentación.

  2. (Opcional) Si tu plugin necesita una referencia a Activity, implementa también la interfaz ActivityAware.

  3. (Opcional) Si se espera que tu plugin se mantenga en un Service en segundo plano en algún momento, implementa la interfaz ServiceAware.

  4. Actualiza el archivo MainActivity.java de la aplicación de ejemplo para usar la v2 de integración de FlutterActivity. Para más detalles, consulta Actualización de proyectos de Android anteriores a 1.12. Es posible que tengas que crear un constructor público para la clase de tu plugin si aún no existía uno. Por ejemplo:

    MainActivity.java
    java
     package io.flutter.plugins.firebasecoreexample;
    
     import io.flutter.embedding.android.FlutterActivity;
     import io.flutter.embedding.engine.FlutterEngine;
     import io.flutter.plugins.firebase.core.FirebaseCorePlugin;
    
     public class MainActivity extends FlutterActivity {
       // You can keep this empty class or remove it. Plugins on the new embedding
       // now automatically registers plugins.
     }
    
  5. (Opcional) Si eliminaste MainActivity.java, actualiza el archivo <nombre_del_plugin>/example/android/app/src/main/AndroidManifest.xml para usar io.flutter.embedding.android.FlutterActivity. Por ejemplo:

    AndroidManifest.xml
    xml
     <activity android:name="io.flutter.embedding.android.FlutterActivity"
            android:theme="@style/LaunchTheme"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale"
            android:hardwareAccelerated="true"
            android:exported="true"
            android:windowSoftInputMode="adjustResize">
            <meta-data
                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
                android:value="true" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    
  6. (Opcional) Crea un archivo EmbeddingV1Activity.java que use la integración v1 para el proyecto de ejemplo en la misma carpeta que MainActivity para seguir probando la compatibilidad de la integración v1 con tu plugin. Ten en cuenta que debes registrar manualmente todos los plugins en lugar de usar GeneratedPluginRegistrant. Por ejemplo:

    EmbeddingV1Activity.java
    java
    package io.flutter.plugins.batteryexample;
    
    import android.os.Bundle;
    import io.flutter.app.FlutterActivity;
    import io.flutter.plugins.battery.BatteryPlugin;
    
    public class EmbeddingV1Activity extends FlutterActivity {
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        BatteryPlugin.registerWith(registrarFor("io.flutter.plugins.battery.BatteryPlugin"));
      }
    }
    
  7. Agrega <meta-data android:name="flutterEmbedding" android:value="2"/> al archivo <nombre_del_plugin>/example/android/app/src/main/AndroidManifest.xml. Esto configura la aplicación de ejemplo para usar la integración v2.

  8. (Opcional) Si creaste un EmbeddingV1Activity en el paso anterior, agrega el EmbeddingV1Activity al archivo <nombre_del_plugin>/example/android/app/src/main/AndroidManifest.xml. Por ejemplo:

    AndroidManifest.xml
    xml
    <activity
        android:name=".EmbeddingV1Activity"
        android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale"
        android:hardwareAccelerated="true"
        android:exported="true"
        android:windowSoftInputMode="adjustResize">
    </activity>
    

Probando tu plugin

#

Los pasos restantes abordan las pruebas de tu plugin, lo cual recomendamos pero no es obligatorio.

  1. Actualiza el archivo <nombre_del_plugin>/example/android/app/build.gradle para reemplazar las referencias a android.support.test con androidx.test:

    build.gradle
    groovy
    defaultConfig {
      ...
      testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
      ...
    }
    
    build.gradle
    groovy
    dependencies {
    ...
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test:rules:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    ...
    }
    
  2. Agrega archivos de prueba para MainActivity y EmbeddingV1Activity en <nombre_del_plugin>/example/android/app/src/androidTest/java/<ruta_del_plugin>/. Deberás crear estos directorios. Por ejemplo:

    MainActivityTest.java
    java
    package io.flutter.plugins.firebase.core;
    
    import androidx.test.rule.ActivityTestRule;
    import io.flutter.plugins.firebasecoreexample.MainActivity;
    import org.junit.Rule;
    import org.junit.runner.RunWith;
    
    @RunWith(FlutterRunner.class)
    public class MainActivityTest {
      // Replace `MainActivity` with `io.flutter.embedding.android.FlutterActivity` if you removed `MainActivity`.
      @Rule public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class);
    }
    
    EmbeddingV1ActivityTest.java
    java
    package io.flutter.plugins.firebase.core;
    
    import androidx.test.rule.ActivityTestRule;
    import io.flutter.plugins.firebasecoreexample.EmbeddingV1Activity;
    import org.junit.Rule;
    import org.junit.runner.RunWith;
    
    @RunWith(FlutterRunner.class)
    public class EmbeddingV1ActivityTest {
      @Rule
      public ActivityTestRule<EmbeddingV1Activity> rule =
          new ActivityTestRule<>(EmbeddingV1Activity.class);
    }
    
  3. Agrega las dev_dependencies integration_test y flutter_driver a <nombre_del_plugin>/pubspec.yaml y <nombre_del_plugin>/example/pubspec.yaml.

    pubspec.yaml
    yaml
    integration_test:
      sdk: flutter
    flutter_driver:
      sdk: flutter
    
  4. Actualiza la versión mínima de Flutter del entorno en <nombre_del_plugin>/pubspec.yaml. Todos los plugins en adelante establecerán la versión mínima en 1.12.13+hotfix.6, que es la versión mínima para la cual podemos garantizar el soporte. Por ejemplo:

    pubspec.yaml
    yaml
    environment:
      sdk: ">=2.16.1 <3.0.0"
      flutter: ">=1.17.0"
    
  5. Crea una prueba simple en <nombre_del_plugin>/test/<nombre_del_plugin>_test.dart. Con el propósito de probar el PR que agrega el soporte de integración v2, estamos intentando probar alguna funcionalidad muy básica del plugin. Esta es una prueba de humo para asegurar que el plugin se registra correctamente con el nuevo embedder. Por ejemplo:

    dart
    import 'package:flutter_test/flutter_test.dart';
    import 'package:integration_test/integration_test.dart';
    
    void main() {
      IntegrationTestWidgetsFlutterBinding.ensureInitialized();
    
      testWidgets('Can get battery level', (tester) async {
        final Battery battery = Battery();
        final int batteryLevel = await battery.batteryLevel;
        expect(batteryLevel, isNotNull);
      });
    }
    
  6. Ejecuta de prueba las pruebas integration_test localmente. En una terminal, haz lo siguiente:

    flutter test integration_test/app_test.dart
    

Plugin básico

#

Para comenzar con un plugin de Flutter para Android en código, empieza por implementar FlutterPlugin.

java
public class MyPlugin implements FlutterPlugin {
  @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
    // TODO: your plugin is now attached to a Flutter experience.
  }

  @Override
  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
    // TODO: your plugin is no longer attached to a Flutter experience.
  }
}

Como se muestra arriba, tu plugin podría (o no) estar asociado con una experiencia de Flutter determinada en cualquier momento dado. Debes tener cuidado de inicializar el comportamiento de tu plugin en onAttachedToEngine(), y luego limpiar las referencias de tu plugin en onDetachedFromEngine().

El FlutterPluginBinding proporciona a tu plugin algunas referencias importantes:

binding.getFlutterEngine()

Devuelve el FlutterEngine al que está adjunto tu plugin, proporcionando acceso a componentes como DartExecutor, FlutterRenderer y más.

binding.getApplicationContext()

Devuelve el Context de la aplicación de Android para la aplicación en ejecución.

Plugin de UI/Activity

#

Si tu plugin necesita interactuar con la UI, como solicitar permisos o alterar los elementos visuales de la UI de Android, entonces debes seguir pasos adicionales para definir tu plugin. Debes implementar la interfaz ActivityAware.

java
public class MyPlugin implements FlutterPlugin, ActivityAware {
  //...normal plugin behavior is hidden...

  @Override
  public void onAttachedToActivity(ActivityPluginBinding activityPluginBinding) {
    // TODO: your plugin is now attached to an Activity
  }

  @Override
  public void onDetachedFromActivityForConfigChanges() {
    // TODO: the Activity your plugin was attached to was
    // destroyed to change configuration.
    // This call will be followed by onReattachedToActivityForConfigChanges().
  }

  @Override
  public void onReattachedToActivityForConfigChanges(ActivityPluginBinding activityPluginBinding) {
    // TODO: your plugin is now attached to a new Activity
    // after a configuration change.
  }

  @Override
  public void onDetachedFromActivity() {
    // TODO: your plugin is no longer associated with an Activity.
    // Clean up references.
  }
}

Para interactuar con una Activity, tu plugin ActivityAware debe implementar el comportamiento adecuado en 4 etapas. Primero, tu plugin se vincula a una Activity. Puedes acceder a esa Activity y a varios de sus callbacks a través del ActivityPluginBinding proporcionado.

Dado que las Activitys pueden destruirse durante los cambios de configuración, debes limpiar cualquier referencia a la Activity dada en onDetachedFromActivityForConfigChanges(), y luego restablecer esas referencias en onReattachedToActivityForConfigChanges().

Finalmente, en onDetachedFromActivity() tu plugin debe limpiar todas las referencias relacionadas con el comportamiento de la Activity y volver a una configuración sin UI.