Android: Stamps y Visage SDK en Flutter
Esta guía documenta cómo integrar las librerías Android: Stamps SDK y Visage SDK de JAAK en un proyecto Flutter.
Prerrequisitos
- Flutter instalado (versión recomendada: 3.x o superior)
- JDK 17 instalado
- Android Studio o herramientas de Android configuradas
- Dispositivo Android físico (los SDKs no funcionan bien en emuladores)
Crear Proyecto Flutter
Si aún no tienes un proyecto Flutter, créalo:
flutter create nombre_proyecto
cd nombre_proyecto
Configuración de pubspec.yaml
Archivo: pubspec.yaml
El pubspec.yaml
NO requiere dependencias especiales para esta integración, ya que usamos Platform Channels nativos.
name: nombre_proyecto
description: Proyecto Flutter con Stamps y Visage SDK
environment:
sdk: ^3.9.2
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
Nota: No se necesitan plugins de Flutter adicionales porque la integración se hace completamente mediante Platform Channels.
Versiones Requeridas
IMPORTANTE: Estas versiones deben coincidir con el proyecto Android de referencia para garantizar compatibilidad.
Versiones del Proyecto Android Original:
- Kotlin: 1.9.22
- Hilt: 2.46
- Java: VERSION_17 (JDK 17)
- minSdk: 24
- compileSdk: 35
- Gradle Plugin: 8.3.2 (compatible con 8.9.1)
- Stamps SDK: 1.0.0.beta
- Visage SDK: 1.0.0.beta
Paso 1: Configurar Repositorios Maven
Archivo: android/build.gradle.kts
allprojects {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
maven { url = uri("https://us-maven.pkg.dev/jaak-platform/jaak-android") }
}
}
Paso 2: Configurar Plugins en settings.gradle.kts
Archivo: android/settings.gradle.kts
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
id("com.google.dagger.hilt.android") version "2.46" apply false
}
Paso 3: Configurar build.gradle.kts de la App
Archivo: android/app/build.gradle.kts
Plugins:
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-kapt")
id("com.google.dagger.hilt.android")
id("dev.flutter.flutter-gradle-plugin")
}
Configuración Android:
android {
namespace = "com.example.tu_app"
compileSdk = 35 // o flutter.compileSdkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
defaultConfig {
minSdk = 24
targetSdk = 33
// ... resto de configuración
}
}
Dependencias:
dependencies {
// AndroidX Core
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.fragment:fragment-ktx:1.6.2")
implementation("androidx.activity:activity-ktx:1.8.2")
// Hilt - Inyección de dependencias (REQUERIDO)
implementation("com.google.dagger:hilt-android:2.46")
kapt("com.google.dagger:hilt-android-compiler:2.46")
// SDKs de JAAK
implementation("com.jaak.stampssdk:jaakstamps-sdk:1.0.0.beta")
implementation("com.jaak.visagesdk:jaakvisage-sdk:1.0.0.beta")
// Google ML Kit
implementation("com.google.android.gms:play-services-mlkit-face-detection:17.1.0")
implementation("com.google.android.gms:play-services-location:21.0.1")
// CameraX
implementation("androidx.camera:camera-camera2:1.4.1")
implementation("androidx.camera:camera-lifecycle:1.4.1")
implementation("androidx.camera:camera-core:1.4.1")
implementation("androidx.camera:camera-view:1.4.1")
implementation("androidx.camera:camera-video:1.4.1")
// Guava
implementation("com.google.guava:guava:32.1.3-android")
}
Paso 4: Configurar AndroidManifest.xml
Archivo: android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Permisos -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"/>
<!-- Características de hardware -->
<uses-feature android:name="android.hardware.camera.any" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<application
android:name=".MyApplication"
tools:replace="android:label,android:name"
...>
<!-- MainActivity principal -->
<activity android:name=".MainActivity" ... />
<!-- Actividades para SDKs -->
<activity
android:name=".StampsActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".VisageActivity"
android:exported="false"
android:screenOrientation="portrait" />
<!-- FileProvider -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="android:authorities">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
Paso 5: Crear MyApplication.kt con Hilt
Archivo: android/app/src/main/kotlin/com/example/tu_app/MyApplication.kt
package com.example.tu_app
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MyApplication : Application()
Paso 6: Crear Actividades para SDKs
StampsActivity.kt
Archivo: android/app/src/main/kotlin/com/example/tu_app/StampsActivity.kt
package com.example.tu_app
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.jaak.stampssdk.sdk.StampsSDK
import com.jaak.stampssdk.ui.adapter.StampsListener
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class StampsActivity : AppCompatActivity(), StampsListener {
private lateinit var stampsSDK: StampsSDK
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
stampsSDK = StampsSDK(this, this)
val documentType = intent.getIntExtra("documentType", 1)
stampsSDK.startStamps(documentType)
}
override fun onErrorStamps(text: String) {
val resultIntent = Intent()
resultIntent.putExtra("error", text)
setResult(Activity.RESULT_CANCELED, resultIntent)
finish()
}
override fun onSuccessStamps(
typeProcess: Int,
frontOriginalUri: Uri?,
frontCropUri: Uri?,
backOriginalUri: Uri?,
backCropUri: Uri?
) {
val resultIntent = Intent()
resultIntent.putExtra("typeProcess", typeProcess.toString())
resultIntent.putExtra("frontOriginalUri", frontOriginalUri?.toString())
resultIntent.putExtra("frontCropUri", frontCropUri?.toString())
resultIntent.putExtra("backOriginalUri", backOriginalUri?.toString())
resultIntent.putExtra("backCropUri", backCropUri?.toString())
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
}
VisageActivity.kt
Archivo: android/app/src/main/kotlin/com/example/tu_app/VisageActivity.kt
package com.example.tu_app
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.jaak.visagesdk.ui.view.VisageSDK
import com.jaak.visagesdk.ui.adapter.VisageListener
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class VisageActivity : AppCompatActivity(), VisageListener {
private lateinit var visageSDK: VisageSDK
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
visageSDK = VisageSDK(this, this)
visageSDK.startVisage()
}
override fun onErrorVisage(text: String) {
val resultIntent = Intent()
resultIntent.putExtra("error", text)
setResult(Activity.RESULT_CANCELED, resultIntent)
finish()
}
override fun onSuccessVisage(typeProcess: Int, uri: Uri?) {
val resultIntent = Intent()
resultIntent.putExtra("typeProcess", typeProcess.toString())
resultIntent.putExtra("videoUri", uri?.toString())
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
}
Paso 7: Modificar MainActivity.kt
Archivo: android/app/src/main/kotlin/com/example/tu_app/MainActivity.kt
package com.example.tu_app
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import dagger.hilt.android.AndroidEntryPoint
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
@AndroidEntryPoint
class MainActivity : FlutterActivity() {
private val STAMPS_CHANNEL = "com.example.stamps/channel"
private val VISAGE_CHANNEL = "com.example.visage/channel"
private val STAMPS_REQUEST_CODE = 1001
private val VISAGE_REQUEST_CODE = 1002
private var stampsResult: MethodChannel.Result? = null
private var visageResult: MethodChannel.Result? = null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// Configurar canal para Stamps
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, STAMPS_CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"startStampsCapture" -> {
stampsResult = result
val documentType = call.argument<Int>("documentType") ?: 1
startStampsActivity(documentType)
}
else -> result.notImplemented()
}
}
// Configurar canal para Visage
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, VISAGE_CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"startVisageDetection" -> {
visageResult = result
startVisageActivity()
}
else -> result.notImplemented()
}
}
}
private fun startStampsActivity(documentType: Int) {
val intent = Intent(this, StampsActivity::class.java)
intent.putExtra("documentType", documentType)
startActivityForResult(intent, STAMPS_REQUEST_CODE)
}
private fun startVisageActivity() {
val intent = Intent(this, VisageActivity::class.java)
startActivityForResult(intent, VISAGE_REQUEST_CODE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
STAMPS_REQUEST_CODE -> {
if (resultCode == Activity.RESULT_OK && data != null) {
val result = mapOf(
"success" to true,
"typeProcess" to data.getStringExtra("typeProcess"),
"frontOriginalUri" to data.getStringExtra("frontOriginalUri"),
"frontCropUri" to data.getStringExtra("frontCropUri"),
"backOriginalUri" to data.getStringExtra("backOriginalUri"),
"backCropUri" to data.getStringExtra("backCropUri")
)
stampsResult?.success(result)
} else {
val errorMessage = data?.getStringExtra("error") ?: "Error desconocido en Stamps"
stampsResult?.error("STAMPS_ERROR", errorMessage, null)
}
stampsResult = null
}
VISAGE_REQUEST_CODE -> {
if (resultCode == Activity.RESULT_OK && data != null) {
val result = mapOf(
"success" to true,
"typeProcess" to data.getStringExtra("typeProcess"),
"videoUri" to data.getStringExtra("videoUri")
)
visageResult?.success(result)
} else {
val errorMessage = data?.getStringExtra("error") ?: "Error desconocido en Visage"
visageResult?.error("VISAGE_ERROR", errorMessage, null)
}
visageResult = null
}
}
}
}
Paso 8: Crear file_paths.xml
Archivo: android/app/src/main/res/xml/file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="cache" path="." />
<files-path name="files" path="." />
<external-path name="external" path="." />
<external-cache-path name="external_cache" path="." />
<external-files-path name="external_files" path="." />
</paths>
Paso 9: Crear Servicios Dart
StampsService
Archivo: lib/services/stamps_service.dart
import 'package:flutter/services.dart';
class StampsService {
static const MethodChannel _channel = MethodChannel('com.example.stamps/channel');
static Future<Map<String, dynamic>> startStampsCapture({int documentType = 1}) async {
try {
final result = await _channel.invokeMethod('startStampsCapture', {
'documentType': documentType,
});
if (result is Map) {
return Map<String, dynamic>.from(result);
}
throw Exception('Formato de respuesta inválido');
} on PlatformException catch (e) {
throw Exception('Error en Stamps: ${e.message}');
}
}
}
VisageService
Archivo: lib/services/visage_service.dart
import 'package:flutter/services.dart';
class VisageService {
static const MethodChannel _channel = MethodChannel('com.example.visage/channel');
static Future<Map<String, dynamic>> startVisageDetection() async {
try {
final result = await _channel.invokeMethod('startVisageDetection');
if (result is Map) {
return Map<String, dynamic>.from(result);
}
throw Exception('Formato de respuesta inválido');
} on PlatformException catch (e) {
throw Exception('Error en Visage: ${e.message}');
}
}
}
Paso 10: Crear Modelos Dart
StampsResult
Archivo: lib/models/stamps_result.dart
class StampsResult {
final bool success;
final String? typeProcess;
final String? frontOriginalUri;
final String? frontCropUri;
final String? backOriginalUri;
final String? backCropUri;
StampsResult({
required this.success,
this.typeProcess,
this.frontOriginalUri,
this.frontCropUri,
this.backOriginalUri,
this.backCropUri,
});
factory StampsResult.fromMap(Map<String, dynamic> map) {
return StampsResult(
success: map['success'] ?? false,
typeProcess: map['typeProcess'],
frontOriginalUri: map['frontOriginalUri'],
frontCropUri: map['frontCropUri'],
backOriginalUri: map['backOriginalUri'],
backCropUri: map['backCropUri'],
);
}
Map<String, dynamic> toMap() {
return {
'success': success,
'typeProcess': typeProcess,
'frontOriginalUri': frontOriginalUri,
'frontCropUri': frontCropUri,
'backOriginalUri': backOriginalUri,
'backCropUri': backCropUri,
};
}
@override
String toString() {
return 'StampsResult(success: $success, typeProcess: $typeProcess, frontCropUri: $frontCropUri)';
}
}
VisageResult
Archivo: lib/models/visage_result.dart
class VisageResult {
final bool success;
final String? typeProcess;
final String? videoUri;
VisageResult({
required this.success,
this.typeProcess,
this.videoUri,
});
factory VisageResult.fromMap(Map<String, dynamic> map) {
return VisageResult(
success: map['success'] ?? false,
typeProcess: map['typeProcess'],
videoUri: map['videoUri'],
);
}
Map<String, dynamic> toMap() {
return {
'success': success,
'typeProcess': typeProcess,
'videoUri': videoUri,
};
}
@override
String toString() {
return 'VisageResult(success: $success, typeProcess: $typeProcess, videoUri: $videoUri)';
}
}
Checklist de Integración
Configuración Android
- Versiones de Kotlin (1.9.22), Hilt (2.46) y Java (17) coinciden
- Repositorios Maven configurados en
android/build.gradle.kts
- Plugins de Hilt agregados en
android/settings.gradle.kts
- Dependencias agregadas en
android/app/build.gradle.kts
- Permisos agregados en
AndroidManifest.xml
-
MyApplication.kt
creado con@HiltAndroidApp
-
StampsActivity.kt
yVisageActivity.kt
creados con@AndroidEntryPoint
-
MainActivity.kt
actualizado con Platform Channels completos -
file_paths.xml
creado enres/xml/
Código Flutter/Dart
- Servicios Dart creados (
lib/services/stamps_service.dart
,lib/services/visage_service.dart
) - Modelos Dart creados (
lib/models/stamps_result.dart
,lib/models/visage_result.dart
) - Integración en
main.dart
o widget principal
Validación
- Proyecto compila sin errores
- Dispositivo Android físico conectado
- Permisos de cámara funcionando
- SDKs responden correctamente
Notas Importantes
Hilt es OBLIGATORIO: Los SDKs de Stamps y Visage requieren Hilt para inyección de dependencias.
Versiones deben coincidir: Para evitar conflictos, usa las mismas versiones que el proyecto Android de referencia.
Dispositivo físico requerido: Los SDKs requieren cámara real, no funcionan bien en emuladores.
Java 17: Asegúrate de tener JDK 17 instalado en tu sistema.
Problemas Comunes
Error: Hilt Activity must be attached to @HiltAndroidApp Application
Solución: Verifica que:
- MyApplication.kt tenga
@HiltAndroidApp
- AndroidManifest.xml use
android:name=".MyApplication"
- StampsActivity y VisageActivity tengan
@AndroidEntryPoint
Error: Cannot access AppCompatActivity
Solución: Verifica que las dependencias de AndroidX estén agregadas en build.gradle.kts
Error de compilación con Kotlin
Solución: Asegúrate de usar Kotlin 1.9.22 exactamente
Error: PlatformException Stamps_ERROR o VISAGE_ERROR
Posibles causas:
- Permisos de cámara no concedidos
- SDK no inicializado correctamente
- Dispositivo emulador (usar dispositivo físico)
Solución:
- Verificar permisos en tiempo de ejecución
- Verificar que Hilt esté configurado correctamente
- Revisar logs de Android para detalles específicos
Error: MissingPluginException
Causa: Method Channels no están registrados correctamente
Solución: Verificar que MainActivity.kt tenga el código completo de configuración de canales
Error: NoClassDefFoundError para clases de Jaak
Causa: Dependencias de Jaak no están disponibles
Solución:
- Verificar que los repositorios Maven estén configurados
- Limpiar y reconstruir el proyecto:
flutter clean && flutter pub get
- Verificar conectividad a repositorios Maven
Error: Aplicación se cierra al usar SDKs
Causa: Usualmente problemas de permisos o configuración de Hilt
Solución:
- Revisar AndroidManifest.xml tiene todos los permisos
- Verificar que todas las Activities tengan
@AndroidEntryPoint
- Verificar que MyApplication tenga
@HiltAndroidApp
Tips de Debugging
- Usar Android Studio Logcat para ver errores detallados
- Verificar permisos antes de usar cada SDK:
import 'package:permission_handler/permission_handler.dart'; if (await Permission.camera.request().isGranted) { // Usar SDK }
- Test en dispositivo físico - los emuladores no soportan bien estos SDKs
Estructura de Archivos del Proyecto Flutter
Después de completar la integración, tu proyecto debería tener esta estructura:
nombre_proyecto/
├── android/
│ ├── app/
│ │ ├── src/main/
│ │ │ ├── kotlin/com/example/tu_app/
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── MyApplication.kt
│ │ │ │ ├── StampsActivity.kt
│ │ │ │ └── VisageActivity.kt
│ │ │ ├── res/xml/
│ │ │ │ └── file_paths.xml
│ │ │ └── AndroidManifest.xml
│ │ └── build.gradle.kts
│ ├── build.gradle.kts
│ └── settings.gradle.kts
├── lib/
│ ├── models/
│ │ ├── stamps_result.dart
│ │ └── visage_result.dart
│ ├── services/
│ │ ├── stamps_service.dart
│ │ └── visage_service.dart
│ └── main.dart
└── pubspec.yaml
Ejecutar el Proyecto
1. Instalar dependencias de Flutter:
flutter pub get
2. Conectar dispositivo Android físico con USB Debug activado
3. Verificar que el dispositivo sea detectado:
flutter devices
4. Ejecutar la aplicación:
flutter run
O desde Android Studio:
- Abre el proyecto Flutter
- Selecciona el dispositivo
- Presiona Run ▶️
Uso en el Código Flutter
Ejemplo completo de uso:
Archivo: lib/main.dart
import 'package:flutter/material.dart';
import 'services/stamps_service.dart';
import 'services/visage_service.dart';
import 'models/stamps_result.dart';
import 'models/visage_result.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Jaak SDK Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Stamps & Visage SDK'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _statusMessage = 'Presiona un botón para comenzar';
bool _isLoading = false;
// Capturar documento con Stamps
Future<void> _capturarDocumento() async {
setState(() {
_isLoading = true;
_statusMessage = 'Iniciando captura de documento...';
});
try {
final result = await StampsService.startStampsCapture(documentType: 1);
final stampsResult = StampsResult.fromMap(result);
if (stampsResult.success) {
setState(() {
_statusMessage = 'Documento capturado exitosamente!\n'
'Tipo: ${stampsResult.typeProcess}\n'
'Imagen frontal: ${stampsResult.frontCropUri != null ? 'Sí' : 'No'}\n'
'Imagen trasera: ${stampsResult.backCropUri != null ? 'Sí' : 'No'}';
});
} else {
setState(() {
_statusMessage = 'Error en la captura del documento';
});
}
} catch (e) {
setState(() {
_statusMessage = 'Error: $e';
});
} finally {
setState(() {
_isLoading = false;
});
}
}
// Detección facial con Visage
Future<void> _deteccionFacial() async {
setState(() {
_isLoading = true;
_statusMessage = 'Iniciando detección facial...';
});
try {
final result = await VisageService.startVisageDetection();
final visageResult = VisageResult.fromMap(result);
if (visageResult.success) {
setState(() {
_statusMessage = 'Detección facial exitosa!\n'
'Tipo: ${visageResult.typeProcess}\n'
'Video: ${visageResult.videoUri != null ? 'Capturado' : 'No disponible'}';
});
} else {
setState(() {
_statusMessage = 'Error en la detección facial';
});
}
} catch (e) {
setState(() {
_statusMessage = 'Error: $e';
});
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Demo de integración Jaak SDKs',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 30),
// Botón Stamps
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton.icon(
onPressed: _isLoading ? null : _capturarDocumento,
icon: const Icon(Icons.document_scanner),
label: const Text('Capturar Documento (Stamps)'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
),
),
const SizedBox(height: 16),
// Botón Visage
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton.icon(
onPressed: _isLoading ? null : _deteccionFacial,
icon: const Icon(Icons.face),
label: const Text('Detección Facial (Visage)'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
),
),
),
const SizedBox(height: 30),
// Indicador de carga
if (_isLoading)
const CircularProgressIndicator(),
const SizedBox(height: 20),
// Mensaje de estado
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey[300]!),
),
child: Text(
_statusMessage,
style: const TextStyle(fontSize: 14),
textAlign: TextAlign.center,
),
),
],
),
),
),
);
}
}
Repositorio de Ejemplo
📦 Puedes apoyarte con un ejemplo funcional en el siguiente repositorio:
Ir a Github
Resumen
Esta integración permite usar los SDKs nativos de Android (Stamps y Visage) desde Flutter usando Platform Channels. Las versiones deben coincidir con el proyecto Android original para garantizar compatibilidad.
Puntos clave:
- ✅ No requiere plugins de Flutter adicionales
- ✅ Usa Platform Channels nativos
- ✅ Requiere Hilt (inyección de dependencias)
- ✅ Requiere dispositivo Android físico
- ✅ Las versiones deben coincidir con el proyecto Android de referencia
Updated about 2 hours ago