Projeto para AndroidStudio como forma de material de estudo que implementa leitura de dados NDEF de uma tag NFC. Criei esse repositório de uso pessoal para entender e aperfeiçoar melhor o uso de etiquetas de aproximação de tecnologia NFC.
Passo a passo de como implementar em um projeto.
Um aplicativo possui suas permissões e para esse recurso funcionar, precisamos uma permissão e um recurso para utilizá-lo. Isso é feito no arquivo de manifesto.
Adicione essas linhas antes da tag <application>
e dentro da tag <manifest>
:
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
Ao criar uma nova activity, ela será declarada no arquivo de manifesto. As activitys podem receber um campo no manifesto que é o <intent_filter>
. Quando a activity é a principal/launcher, por padrão, já vem esse campo sinalizando essa característica.
Precisamos acrescentar os filtros desejados das intenções que queremos com a NFC nessa activity. Nesse caso em especial, vamos usar apenas a TAG_DISCOVERED. Para isso, adicionar essa linha:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<!-- Adicionar essa linha -->
<action android:name="android.nfc.action.TAG_DISCOVERED" />
</intent-filter>
Agora vamos para o código em Kotlin. NfcAdapter é uma classe necessária para detectar tags NFC e realizar operações de escrita e leitura de dados. Ele atua como uma interface entre hardware NFC. Ele é necessário pois ele habilita/desabilita a comunicação NFC, registra os filtros e notificações.
Essa classe possui o método getDefaultAdapter() que é usado para obter acesso do adaptador do dispositivo e interagir. Usar NfcAdapter.getDefaultAdapter(this)
verifica se o dispositivo suporta NFC e se está atividado, retornando uma instância associada ao dispositivo. Então, criamos uma variável qualquer para receber essa instância e assim usar os dados recebidos nela.
Nesse caso, vamos criar uma instância chamada nfcAdapter (letra "n" em minúsculo) que recebe esse valor. Importante saber que pode receber nulo caso o dispositivo não está ativado e esse nulo deve ser verificado.
Para isso, no ciclo de vida onCreate() vamos obter essa referência, desta forma:
private var nfcAdapter: NfcAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
// ...
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
}
Um PendingIntent é uma classe que suas instâncias retornam a intenção e o alvo da ação para executar. As instâncias podem ser criadas utilizando qualquer um desses métodos que retornam um objeto com as descrições das execuções, que essas execuções podem ser feitas por outras aplicações.
Seu objetivo principal é identificar quando uma tag é descoberta e iniciar a atividade correspondente.
private var pendingIntent: PendingIntent? = null
override fun onCreate(savedInstanceState: Bundle?) {
// ...
pendingIntent = PendingIntent.getActivity(
this,
0,
Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_IMMUTABLE
)
}
// Esse método possui essa estrutura: getActivity(Context, int, Intent, int)
Registrar os filtros é uma prática comum para informar o aplicativo/sistema quais tipos de ações nossa activity vai lidar e ter mais controle. Esse registro acontece usando alguns tipos de eventos:
- ACTION_NDEF_DISCOVERED: É o mais seguro para se utilizar. Verificar apenas se a tag foi descoberta pode ser muito abrangente. Esse tipo de intent vai servir já para verificar se há documentos no tipo NDEF, facilitando muito as coisas e deixando mais seguro.
- ACTION_TECH_DISCOVERED
- ACTION_TAG_DISCOVERED
Essas constantes são definidas dentro da classe NfcAdapter, portanto, nós chamamos elas através dessa classe, por exemplo (NfcAdapter.ACTION_TAG_DISCOVERED
).
Acima falamos dos intent-filters na linguagem XML, mas em Kotlin, temos a classe chamada IntentFilter. Essa classe por si só define os critérios que uma intenção deve atender, então, ela será usada para filtrar as intenções.
Fazendo na prática, criaremos uma variável ndefFilter que é do tipo IntentFilter que vai receber os filtros e intenções, desta forma:
private var ndefFilter: IntentFilter? = null
override fun onCreate(savedInstanceState: Bundle?) {
// ...
ndefFilter = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED)
}
// Lembrando que NfcAdapter recebeu o valor recebido da tag NFC (dispositivo)
Registrar os filtros servem para configurar as condições nas quais o método onNewIntent()
será chamado. Esse método é chamado pelo sistema na detecção de uma nova intenção, o que será usado no próximo passo para manipular os dados. Para isso, logo abaixo vamos fazer da seguinte maneira:
val filtros = arrayOf(ndefFilter)
val techLists = arrayOf(arrayOf(Ndef::class.java.name))
nfcAdapter?.enableForegroundDispatch(this, pendingIntent, filters, techLists)
A variável no exemplo anterior ndefFilter recebeu as inteções das tags NDEF que agora passou em forma de array para a variável filtros. A variável techLists é criada dessa forma para simbolizar que queremos trabalhar com tags de tecnologia NDEF.
Com isso, podemos fazer a linha a seguir, que possui um método muito importante. A chamada enableForegroundDispatch()
é usada para registrar os filtros de intenção e o PendinIntent utilizando a variável nfcAdapter, permitindo que a atividade intercepte intenções de tags NFC descobertas enquanto está em primeiro plano. Em outras palavras, configurando as condições conforme foi dito acima.
Isso ocorre através da chamada do passo anterior onNewIntent(). A obtenção de dados da tag pode ser feito da seguinte forma:
intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
Essa função por inteiro podemos definir em nosso código da seguinte forma:
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent?.action) {
intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)?.also { rawMessages ->
val dadosMensagens: List<NdefMessage> = rawMessages.map { it as NdefMessage }
// Finalmente: Manipulação de dados!
var i: List<NdefMessage> = dadosMensagens
}
}
}
Vemos dentro dessa função que é passado uma variável intent que é utilizada várias vezes e é essencial para resgatar as informações. Ela vem através dos registros que foram feitos anteriormente com enableForegroundDispatch()
. Como dito anteriormente, preparou-se as configurações nos registros.
Esses filtros registrados no passo 6 devem ser desregistrados em algum ciclo de vida de encerramento (pause ou destroy). Para isso, é só usar o método contrário, funcionando desta forma:
override fun onPause() {
super.onPause()
nfcAdapter?.disableForegroundDispatch(this)
}