플러터에서 NFC 사용하기

나고수·2024년 1월 2일
0

플러터

목록 보기
4/5
post-thumbnail

플러터에서 NFC 사용하기

NFC 라이브러리는 nfc_manager를 사용하였습니다.

상황 : NFC 읽은 후 쓰기를 연속으로 해야함

NDEF 구조

  • Ndef : NFC 데이터 교환에 이용되는 데이터 교환 포맷

권한 설정

NFC 읽기 및 쓰기

import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:android_intent_plus/android_intent.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:nfc_manager/nfc_manager.dart';
import 'package:nfc_manager/platform_tags.dart';
import 'package:redixplus/auth_util/toast_util.dart';

import '../../const/const.dart';
import '../util.dart';

Future<void> readAndWriteNfc() async {
  //nfc 사용이 가능한지 확인
  bool isAvailable = await NfcManager.instance.isAvailable();

  //nfc 사용이 불가능한 경우
  if (!isAvailable) {
    if (Platform.isAndroid) {
      //android 일 경우 nfc 설정창으로 이동 시킴
      //ios는 nfc가 os단에서 감지하고 실행되기 때문에 안드로이드처럼 설정창으로 보낼 수 없다.
      const AndroidIntent intent = AndroidIntent(
        action: 'android.settings.NFC_SETTINGS',
      );
      intent.launch();
      return;
    }
    //토스트 띄우기
    ToastUtil.show(message: NFC_DISALBE_DEVICE_MESSAGE);
    return;
  }
  await nfcRead();
}

Future<void> nfcRead() async {
  NfcManager.instance.startSession(
    //nfc 태그를 읽을 수 있는 타입을 지정한다.
    //타입에 따라 읽을 수 있는 nfc 태그가 달라진다. (iso14443는 교통카드, iso15693는 물류,재고관리 등에 쓰인다고 한다)
    //pollingOptions을 지정하지 않으면 모든 타입의 nfc 태그를 읽을 수 있다.
    //특정 태그만 읽어야 하는 기능은 아니라서 아래 줄은 필요 없을 것 같다.
    //pollingOptions: {NfcPollingOption.iso14443, NfcPollingOption.iso15693},

    //ios에서만 사용되는 옵션
    alertMessage: NFC_SCAN_MESSAGE,
    onError: (NfcError error) {
      //에러 시 세션 멈춤
      return NfcManager.instance.stopSession(alertMessage: NFC_SCAN_FAIL);
    },

    //nfc 태그를 읽었을 때
    onDiscovered: (NfcTag tag) async {
      //nfc id 가져오기
      String id = getNfcId(tag);
      //nfc 페이로드 가져오기
      String? decodedNfcMessage = getDecodedNfcMessage(tag);
      //ios만 세션 스탑 해줌
      //ios는 stop 안하면 팝업이 계속 뜸
      //android는 stop 하면 nfc가 연속적으로 읽힘
      if (isIos) {
        NfcManager.instance.stopSession(alertMessage: NFC_SCAN_SUCCESS);
      }
      //nfc write
      await nfcWrite('hello world!', 'https://www.naver.com');
    },
  );
}

Future<void> nfcWrite(String stringData, String url) async {
  if (isIos) {
    //ios는 연속적인 nfc scan이 안되서 몇초간 딜레이를 준 후 실행
    await Future.delayed(const Duration(milliseconds: 4000), () {});
  }
  NfcManager.instance.startSession(
    //pollingOptions: {NfcPollingOption.iso14443, NfcPollingOption.iso15693},
    alertMessage: NFC_SCAN_MESSAGE,
    onDiscovered: (NfcTag tag) async {
      var ndef = Ndef.from(tag);
      //nfc 태그가 쓰기가 가능한지 확인
      if (ndef == null || !ndef.isWritable) {
        debugPrint('NFC DISABLE WRITE TYPE');
        //nfc 태그가 쓰기가 불가능하면 세션 멈춤
        NfcManager.instance.stopSession(errorMessage: NFC_SCAN_FAIL);
        return;
      }

      //nfc 태그에 쓸 메시지 생성
      NdefMessage message = NdefMessage(
        [
          NdefRecord.createUri(
            Uri.parse(
              url,
            ),
          ),
          NdefRecord.createMime(
            'text/plain',
            Uint8List.fromList(
              stringData.codeUnits,
            ),
          ),
        ],
      );

      //nfc 태그에 쓰기
      await ndef.write(message);
      //ios만 세션 스탑 해줌
      //ios는 stop 안하면 팝업이 계속 뜸
      //android는 stop 하면 nfc가 연속적으로 읽힘
      if (isIos) {
        NfcManager.instance.stopSession(alertMessage: NFC_SCAN_SUCCESS);
      }
    },
  );
}

String getNfcId(NfcTag tag) {
  //ios 일 경우
  if (isIos) {
    //Mifare는 ios 에서 사용되는 NFCMiFareTag 클래스에 접근하게 해줌
    var mifare = MiFare.from(tag);
    return mifare!.identifier
        .map((e) => e.toRadixString(16).padLeft(2, '0'))
        .join('');
  } else {
    //android 일 경우
    Ndef? ndef = Ndef.from(tag);
    return ndef?.additionalData['identifier']
        .map((e) => e.toRadixString(16).padLeft(2, '0'))
        .join('');
  }
}

//nfc 페이로드 가져오기
String? getDecodedNfcMessage(NfcTag tag) {
  //ndef = NFC 데이터 교환에 이용되는 데이터 교환 포맷
  Ndef? ndef = Ndef.from(tag);
  NdefMessage? nfcMessage = ndef?.cachedMessage;
  //ndef message > ndef record > header(record에 대한 기본정보) + payload(첫 바이트는 페이로드의 헤더, 나머지는 페이로드 데이터)
  //페이로드의 타입이 text/plain 인 것을 찾는다
  NdefRecord? textTypeData = nfcMessage?.records
      .firstWhereOrNull((e) => utf8.decode(e.type) == 'text/plain');

  if (textTypeData != null) {
    Uint8List uint8ListData = Uint8List.fromList(textTypeData.payload);
    //페이로드의 첫 바이트는 페이로드의 헤더이므로 제외하고 utf8로 디코딩한다.
    return utf8.decode(uint8ListData.sublist(1));
  }
  return null;
}
profile
되고싶다

0개의 댓글