Flutter y NFC
Usar NFC no es una tarea fácil y la documentación es difícil de encontrar sobre ese tema. Entonces, cuando me dieron este proyecto de una StartUp española, he tenido bastantes problemas. Pero de estos problemas, también he aprendido mucho y quiero compartir ese conocimiento contigo y tal vez ayudarte en ahorrar algo de tiempo.
1. Los paquetes
Hay 2 paquetes para manejar NFC en pub.dev: FlutterNfcKit que funciona bien con iOS y NfcInFlutter que funciona mejor con Android. Puedes intentar usar uno de ellos para ambas plataformas, pero verás que solo hay un buen soporte para una plataforma, al menos por DateTime.now(). Además necesitaremos NDEF para codificar y decodificar los registros ndef ya que flutter_nfc_kit no lo implementa.
2. NFC Curso rápido
No explicaré cómo funciona con símbolos y esas cosas, solo voy a exponer lo básico, en caso de que quieras saberlo, si no, puedes saltar a la parte 3.
Básicamente hay 2 tipos de dispositivos NFC, los dispositivos NFC pasivos, como tu tarjeta de crédito, por ejemplo, y los dispositivos activos como…. sí, claro, tu teléfono (si no es demasiado antiguo :). Los dispositivos pasivos no pueden conectarse a otros dispositivos pasivos, solo pueden almacenar cierta información y enviarla a otros dispositivos activos, mientras que los dispositivos activos pueden conectarse con otros dispositivos activos y dispositivos pasivos, leer y escribir datos. Creo que es suficiente, ¡ya has entendido el concepto!
3. Allí vamos
Bien, ahora que sabes que tendrás que manejar 2 paquetes diferentes, probablemente querrás crear un repositorio, y si no lo haces, deberías hacerlo. No soy un gran fan de Apple, pero en este caso, los chips NFC funcionan mucho mejor que los de Android, tengo que admitirlo, así que empezamos con la parte más fácil… iOS con flutter_nfc_kit
En iOS
Primero, tenemos que agregar NFCReaderUsageDescription en tu Info.plist:
<key>NFCReaderUsageDescription</key>
<string>...</string>
Luego, en Xcode, selecciona TARGETS Runner, selecciona la pestaña Signing and Capability y agregue el Near Field Communication Tag Reader.
Y eso es todo para la configuración inicial..
Entonces, empezamos un poco de código, antes de nada, comprobamos que el teléfono móvil tenga un chip NFC, y para eso podemos usar:
await FlutterNfcKit.nfcAvailability;
Con esto viene nuestra primera parte del repo:
Ahora que sabemos cómo manejar los dispositivos sin NFC, continuemos con la inicialización del chip NFC. En iOS, cuando el chip NFC está escuchando, se presenta un modal inferior que puedes personalizar un poco, solo el texto debajo entre la imagen y el botón cancelar.
FlutterNfcKit tiene un método llamado poll() para empezar a escuchar y devolverá un objeto NFCTag después de leer correctamente un Tag, y aquí es donde puedes personalizar la alerta y controlar la tecnología NFC que desea habilitar (readIso14443A, readIso14443B, readIso18092 y readIso15693) :
Cuando el chip recibe la etiqueta, mientras analizas los datos, escribes una nueva etiqueta o cualquier otra cosa, puedes modificar el contenido de iosAlertMessage usando:
await ios.FlutterNfcKit.setIosAlertMessage("Un momento por favor, Estoy haciendo cosas");
Y cuando terminemos, podemos cerrar todo con un nuevo mensaje:
await ios.FlutterNfcKit.finish(iosAlertMessage: "He terminado, adiós!");
Ahora que sabes cómo controlar la alerta, veamos cómo hacer las cosas intermedias, es decir, escribir, limpiar y leer la etiqueta.
Leer la etiqueta
Entonces, hemos recibido la etiqueta del método poll(), si lo deseas, puedes verificar si el tipo de etiqueta es el que desea con la enumeración NFCTagType, por ejemplo:
if (tag.type == ios.NFCTagType.mifare_ultralight){
// do your stuff here
}
Bueno, cada etiqueta tiene una Lista de registros, por lo que para acceder a ellos puedes hacer algo como:
final ndefRecords = await ios.FlutterNfcKit.readNDEFRecords();
//We'll need to decode here the records with de ndef package
for(ndef.NDEFRecord record in ndefRecords){
// Now you have access to
// record.text
// record.language
// record.payload
// and more
}
Ok, en el caso de la aplicación que estaba desarrollando, solo necesitaba obtener un Uuid() como texto, así que quería usar record.text para almacenar el Uuid en el collar Reliqium, pero también puedes obtener datos sin procesar(raw data) con record.payload.
Entonces, para resumirlo en nuestro repositorio:
De forma predeterminada, el collar, necesario para la aplicación que estaba construyendo, está vacío, por lo que la etiqueta que se escribe está escrita por la aplicación, por lo que estoy seguro de que solo hay un TextRecord. Entonces, ahora, aquí viene nuestro siguiente paso, escribir una etiqueta :)
Escribir la etiqueta
Vale, tu dispositivo NFC pasivo está vacío y necesitas escribir alguna identificación, texto u otro tipo de datos en él. Para esto, tendrás que usar el paquete ndef que ofrece diferentes tipos como TextRecord, MimeRecord, TypeRecord, etc., comprueba en tu IDE para ver todos los tipos:
A continuación, se explica cómo escribir una etiqueta con FlutterNfcKit y writeNDEFRecords () que acepta una lista de registros:
Puedes agregar el idioma en TextRecord para satisfacer tus necesidades si lo deseas. Veremos por qué agregamos este MimeRecord más adelante cuando cubramos la parte de Android.
Así que finalmente, así es como se ve nuestro repositorio ahora:
Manejar los errores
Sí, NFC puede arrojar muchos tipos de errores, por lo que debes detectar cada uno de ellos y actuar en consecuencia. Si tiene un error al escribir o leer, recibirá una PlatformException, por lo que solo necesitas detectarlo y verificar el mensaje de error, puede hacer algo como esto:
const writeErrorMessage = "Write NDEF error";
const readErrorMessage = "Read NDEF error";
try{
//All of our previous code is here
[...]
} on PlatformException catch(error) {
if(error.message.contains(writeErrorMessage)) {
//Handle here reading error
} else if (error.message.contains(readErrorMessage)){
//Handle here writing error
} else {
// Handle here the other type of errors
}
}
Y eso es todo para iOS, tenemos casi todo cubierto.
En Android
En primer lugar, asegúrate de agregar el intent-filter en tu AndroidManifest.xml como se menciona en la documentación del paquete:
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Y eso es todo, ahora sigamos el mismo camino que iOS, en primer lugar, verifiquemos la disponibilidad con el plugin NfcInFlutter:
Ok, no hay nada que comentar aquí, solo NFC.isNDEFSupported
Ahora, para leer la etiqueta, el método es NDEF.readNDEF() y devuelve Stream<NDEFMessage>.
Entonces, para manejar este stream, necesitarás una StreamSubscription para suscribirte a los eventos o puedes establecer el flag “once: true” y usar .first en el stream devuelto. A continuación, se muestra un ejemplo sobre cómo implementar estas dos soluciones:
Ok, ahora puedes ver cómo leer el NDEFMessage pero, recuerda lo que dije sobre el registro de Content-type, la razón es que en Android, cuando NFC está activado, hay una aplicación predeterminada en Android que siempre está escuchando, así que cuando escaneas un TAG con tu teléfono, sin ninguna aplicación abierta, verás una ventana como esta:
Entonces, cuando se abre tu aplicación y cuando escaneas una ETIQUETA que contiene un tipo de contenido con el nombre del paquete, anulará el comportamiento predeterminado, pero para eso debes agregar algún código en su AndroidManifest.xml en el intent-filter que hemos agregado antes:
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<!-- add this line-->
<data android:mimeType="application/vnd.com.domain.appname"/>
</intent-filter>
¡Bien, problema resuelto!
Ahora, si quieres escribir un NDEFMessage, puedes usar el mismo principio que para leer un NDEFMessage con el flag “once”
Resumiendo
Implementemos toda la lógica en nuestro repositorio y hacemos un poco de refactoring:
Como puedes ver, para la parte de Android, estoy usando el flag once, honestamente, eso no es ideal porque una vez escaneada, la aplicación NFC de Android predeterminada comenzará a escuchar y detectar el dispositivo NFC y escucharás la notificación del sistema. Si lo has hecho correctamente, la ventana NFC predeterminada no se abrirá, pero aún así, creo que el mejor enfoque es usar StreamSubscription() y cerrarlo con un Future.delayed() una vez que se recibió la etiqueta, pero haz como prefieras. Por ejemplo:
Future<void> close() async {
Future.delayed(const Duration(seconds:5), () => ndefStreamSubscription?.cancel()));
}
Bueno, creo que esta todo por ahora, si quieres ayuda sobre cómo usar este repositorio con Bloc, házmelo saber y escribiré una segunda parte para eso.
Happy coding a todos!