Saltar al contenido principal

Iniciar una actividad de Jetpack Compose desde tu aplicación de Flutter

Aprende a iniciar actividades nativas de Android en tu aplicación de Flutter.

Las actividades nativas de Android te permiten iniciar interfaces de usuario (UI) a pantalla completa ejecutadas íntegramente por y en la plataforma Android. Solo escribirás código Kotlin en esas vistas (aunque podrían enviar mensajes a tu código Dart y recibirlos de él) y tendrás acceso a toda la gama de funcionalidades nativas de Android.

Añadir esta funcionalidad requiere realizar varios cambios en tu aplicación de Flutter y en su aplicación interna generada de Android. En el lado de Flutter, necesitarás crear un nuevo canal de método de plataforma (platform method channel) y llamar a su método invokeMethod. En el lado de Android, necesitarás registrar un MethodChannel nativo equivalente para recibir la señal de Dart y luego iniciar una nueva actividad. Recuerda que todas las aplicaciones de Flutter (cuando se ejecutan en Android) existen dentro de una actividad de Android que es consumida por completo por la aplicación de Flutter. Por lo tanto, como verás en el ejemplo de código, la tarea de la función callback del MethodChannel nativo es iniciar una segunda actividad.

No todas las actividades de Android utilizan Jetpack Compose, pero este tutorial asume que quieres utilizar Compose.

En el lado de Dart

#

En el lado de Dart, crea un canal de método e invócalo desde una interacción de usuario específica, como tocar un botón.

dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

// SECTION 1: START COPYING HERE
const platformMethodChannel = MethodChannel(
  // Note: You can change this string value, but it must match
  // the `CHANNEL` attribute in the next step.
  'com.example.flutter_android_activity',
);
// SECTION 1: END COPYING HERE

void main() {
  runApp(const MainApp());
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  // SECTION 2: START COPYING HERE
  void _launchAndroidActivity() {
    platformMethodChannel.invokeMethod(
      // Note: You can change this value, but it must match
      // the `call.method` value in the next section.
      'launchActivity',

      // Note: You can pass any primitive data types you like.
      // To pass complex types, use package:pigeon to generate
      // matching Dart and Kotlin classes that share serialization logic.
      {'message': 'Hello from Flutter'},
    );
  }
  // SECTION 2: END COPYING HERE

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: const Center(child: Text('Hello World!')),
        floatingActionButton: FloatingActionButton(
          // SECTION 3: Call `_launchAndroidActivity` somewhere.
          onPressed: _launchAndroidActivity,

          // SECTION 3: End
          tooltip: 'Launch Android activity',
          child: const Icon(Icons.launch),
        ),
      ),
    );
  }
}

Hay 3 valores importantes que deben coincidir en tus códigos de Dart y Kotlin:

  1. El nombre del canal (en este ejemplo, el valor es "com.example.flutter_android_activity").
  2. El nombre del método (en este ejemplo, el valor es "launchActivity").
  3. La estructura de los datos que pasa Dart y la estructura de los datos que Kotlin espera recibir. En este caso, los datos son un mapa con una única clave "message".

En el lado de Android

#

Debes realizar cambios en 4 archivos de la aplicación generada de Android para prepararla para iniciar nuevas actividades de Compose.

El primer archivo que requiere modificaciones es android/app/build.gradle.

  1. Añade lo siguiente al bloque android existente:

    android/app/build.gradle.kts
    kotlin
    android {
      // Begin adding here
      buildFeatures {
        compose = true
      }
      composeOptions {
        // https://developer.android.com/jetpack/androidx/releases/compose-kotlin
        kotlinCompilerExtensionVersion = "1.4.8"
      }
      // End adding here
    }
    
    android/app/build.gradle
    groovy
    android {
      // Begin adding here
      buildFeatures {
        compose true
      }
      composeOptions {
        // https://developer.android.com/jetpack/androidx/releases/compose-kotlin
        kotlinCompilerExtensionVersion = "1.4.8"
      }
      // End adding here
    }
    

    Visita el enlace developer.android.com en el fragmento de código y ajusta kotlinCompilerExtensionVersion, según sea necesario. Solo deberías necesitar hacer esto si recibes errores durante flutter run y dichos errores te indican qué versiones están instaladas en tu máquina.

  2. A continuación, añade el siguiente bloque al final del archivo, en el nivel raíz:

    android/app/build.gradle.kts
    kotlin
    dependencies {
        implementation("androidx.core:core-ktx:1.10.1")
        implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
        implementation("androidx.activity:activity-compose")
        implementation(platform("androidx.compose:compose-bom:2024.06.00"))
        implementation("androidx.compose.ui:ui")
        implementation("androidx.compose.ui:ui-graphics")
        implementation("androidx.compose.ui:ui-tooling-preview")
        implementation("androidx.compose.material:material")
        implementation("androidx.compose.material3:material3")
        testImplementation("junit:junit:4.13.2")
        androidTestImplementation("androidx.test.ext:junit:1.1.5")
        androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
        androidTestImplementation(platform("androidx.compose:compose-bom:2024.06.00"))
        androidTestImplementation("androidx.compose.ui:ui-test-junit4")
        androidTestImplementation("androidx.compose.ui:ui-test-junit4")
        debugImplementation("androidx.compose.ui:ui-tooling")
        debugImplementation("androidx.compose.ui:ui-test-manifest")
    }
    
    android/app/build.gradle
    groovy
    dependencies {
        implementation "androidx.core:core-ktx:1.10.1"
        implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
        implementation "androidx.activity:activity-compose"
        implementation platform("androidx.compose:compose-bom:2024.06.00")
        implementation "androidx.compose.ui:ui"
        implementation "androidx.compose.ui:ui-graphics"
        implementation "androidx.compose.ui:ui-tooling-preview"
        implementation "androidx.compose.material:material"
        implementation "androidx.compose.material3:material3"
        testImplementation "junit:junit:4.13.2"
        androidTestImplementation "androidx.test.ext:junit:1.1.5"
        androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1"
        androidTestImplementation platform("androidx.compose:compose-bom:2023.08.00")
        androidTestImplementation "androidx.compose.ui:ui-test-junit4"
        debugImplementation "androidx.compose.ui:ui-tooling"
        debugImplementation "androidx.compose.ui:ui-test-manifest"
    }
    

    El segundo archivo que requiere modificaciones es android/build.gradle.

  3. Añade el siguiente bloque buildscript en la parte superior del archivo:

    android/build.gradle.kts
    kotlin
    buildscript {
        dependencies {
            // Replace with the latest version.
            classpath("com.android.tools.build:gradle:8.1.1")
        }
        repositories {
            google()
            mavenCentral()
        }
    }
    
    android/build.gradle
    groovy
    buildscript {
        dependencies {
            // Replace with the latest version.
            classpath 'com.android.tools.build:gradle:8.1.1'
        }
        repositories {
            google()
            mavenCentral()
        }
    }
    

    El tercer archivo que requiere modificaciones es android/app/src/main/AndroidManifest.xml.

  4. En el bloque de aplicación raíz, añade la siguiente declaración <activity>:

    android/app/src/main/AndroidManifest.xml
    xml
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <application
            android:label="flutter_android_activity"
            android:name="${applicationName}"
            android:icon="@mipmap/ic_launcher">
    
           // START COPYING HERE
            <activity android:name=".SecondActivity" android:exported="true" android:theme="@style/LaunchTheme"></activity>
           // END COPYING HERE
    
           <activity android:name=".MainActivity" …></activity>
          
    </manifest>
    

    El cuarto y último archivo que requiere modificaciones es android/app/src/main/kotlin/com/example/flutter_android_activity/MainActivity.kt. Aquí escribirás el código Kotlin para la funcionalidad deseada de Android.

  5. Añade las importaciones necesarias en la parte superior del archivo:

    MainActivity.kt
    kotlin
    package com.example.flutter_android_activity
    
    import android.content.Intent
    import android.os.Bundle
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.material3.Button
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.Surface
    import androidx.compose.material3.Text
    import androidx.compose.ui.Modifier
    import androidx.core.app.ActivityCompat
    import io.flutter.embedding.android.FlutterActivity
    import io.flutter.embedding.engine.FlutterEngine
    import io.flutter.plugin.common.MethodCall
    import io.flutter.plugin.common.MethodChannel
    import io.flutter.plugins.GeneratedPluginRegistrant
    
  6. Modifica la clase MainActivity generada agregando un campo CHANNEL y un método configureFlutterEngine:

    MainActivity.kt
    kotlin
    class MainActivity: FlutterActivity() {
        // This value must match the `MethodChannel` name in your Dart code.
        private val CHANNEL = "com.example.flutter_android_activity"
    
        override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
            GeneratedPluginRegistrant.registerWith(flutterEngine)
    
            MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
                call: MethodCall, result: MethodChannel.Result ->
                    when (call.method) {
                        // Note: This must match the first parameter passed to
                        // `platformMethodChannel.invokeMethod` in your Dart code.
                        "launchActivity" -> {
                            try {
                                // Takes an object, in this case a String.
                                val message = call.arguments
                                val intent = Intent(this@MainActivity, SecondActivity::class.java)
                                intent.putExtra("message", message.toString())
                                startActivity(intent)
                            } catch (e: Exception){}
                                result.success(true)
                            }
                            else -> {}
                    }
            }
        }
    }
    
  7. Añade una segunda Activity al final del archivo, a la cual hiciste referencia en los cambios anteriores en AndroidManifest.xml:

    MainActivity.kt
    kotlin
    class SecondActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            setContent {
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                    Column {
                        Text(text = "Second Activity")
                        // Note: This must match the shape of the data passed from your Dart code.
                        Text("" + getIntent()?.getExtras()?.getString("message"))
                        Button(onClick = {  finish() }) {
                            Text("Exit")
                        }
                    }
                }
            }
        }
    }
    

Estos pasos muestran cómo iniciar una actividad nativa de Android desde una aplicación de Flutter, lo que a veces puede ser una forma sencilla de conectarse a una funcionalidad específica de Android.