In this lesson, we will dive into writing code. We will show you how to:
Check NFC availability
Scan NFC tags
Implement our game logic
Preparation#
Okay, I have launched our app in the iOS simulator. Notice that the template code generated by react-native-cli
is running. Let's get rid of it.
Create src
directory and a new App.js
inside it. Then, write some basic JSX to center everything on the screen and print "Hello NFC".
Then, go back to the original index.js
, and point the App
from the original one into the new src/App.js
Detecting NFC support#
Next, we would like to detect whether the target mobile device has NFC support or not.
Let's create a hasNfc
state. This state has an initial value to be null
, which means we've not yet confirmed whether NFC is supported or not. Once we do, this state will be a boolean value true
or false
.
Then, we adjust our UI according to its value.
The next step is to actually check the NFC availability. By importing the react-native-nfc-manager
, we can use the isSupported
API, and it will resolve a boolean value to indicate whether the underlying device supports NFC or not.
Since the availability won't change during the whole app lifecycle, we can do this inside a useEffect
hook, with no dependency. This basically simulates the componentDidMount
behavior for React Class component.
And please be aware that the isSupported
API (and most of the APIs for a native module
) is an async
function because our JavaScript code will need to cross the bridge
and ask the corresponding native part for the result.
The final code looks like this:
React.useEffect(() => {
async function checkNfc() {
setHasNfc(await NfcManager.isSupported());
}
checkNfc();
}, []);
We can see the simulator is refreshed, and the result is not supported
.
Let's run the code on a real iOS device. All recent iPhones from iPhone 8 all support NFC.
Once we confirm that the device does support NFC, we can do the native module
initialization by calling the NfcManager.start
function.
React.useEffect(() => {
async function checkNfc() {
const supported = await NfcManager.isSupported();
if (supported) {
await NfcManager.start();
}
setHasNfc(supported);
}
checkNfc();
}, []);
Quick review: We just used the isSupported
API to check the NFC availability, and if the device supports it, we then call start
to initialize our NFC native module.
It's time to start our actual Game
component. Let's quickly create a src/Game.js
by copying the existing code and removing the unnecessary parts.
Then, use this Game
component in our App.js
when NFC is available.
Scanning for our first tag#
Now we're finally ready to scan NFC tags.
First, import NfcManager
and NfcEvents
from react-native-nfc-manager
.
Then, use setEventListener
to listen to NfcEvents.DiscoverTag
event. For now, we just log the message to the console. A small trick here is to use console.warn
for logging rather than console.log
, so we can see the yellow screen
UI popup without enabling the debugger.
The actual tag scanning is through the registerTagEvent
API. Once it discovers any NFC tags, the native side will emit DiscoverTag
event with the tag data to the JavaScript side.
async function scanTag() {
NfcManager.setEventListener(NfcEvents.DiscoverTag, (tag) => {
console.warn('tag found', tag);
});
await NfcManager.registerTagEvent();
};
Okay, let's test it on a real device. Please remember, the NFC antenna for an iPhone is on the top of the device, so the proper position to scan a tag is probably like this picture.
Now let's scan a tag.
Cool, something indeed happened. We can see the iOS NFC prompt pop up once we hit our start button, and the icon changes when the tag is scanned.
Let's see what's in our warning message here. It is an object that contains a ndefMessage
property, which is an array. The object in this array contains properties such as payload
, type
, tnf
, and id
:
{
ndefMessage: [
{
payload: [
4,
114,
101,
97,
99,
116,
110,
97,
116,
105,
118,
101,
46,
100,
101,
118,
],
type: [85],
id: [],
tnf: 1,
},
],
};
Don't worry about them now; we will deep dive into these properties in our next app.
Next, let's move the event listener setup and clean up code into a useEffect
hook.
React.useEffect(() => {
NfcManager.setEventListener(NfcEvents.DiscoverTag, (tag) => {
console.warn(JSON.stringify(tag));
});
return () => {
NfcManager.setEventListener(NfcEvents.DiscoverTag, null);
};
}, []);
Game logic#
It's about time to write our game logic.
The objective of our game is to calculate how much time a player needs to scan five NFC tags. The shorter time means a better score.
First, create a start
state to track when a player hits the start button. Our useEffect
hook should depend on this start
state and re-run the hook logic when it is changed. Because in such a case, it basically means the user re-started the game.
Then, we use a variable called count
inside the closure of our hook to track the remaining count left for a player to scan.
Once the count becomes 0, the game is finished, and we should stop the NFC scanning by calling NfcManager.unregisterTagEvent
and calculate the total elapsed time.
React.useEffect(() => {
let count = 5;
NfcManager.setEventListener(NfcEvents.DiscoverTag, (tag) => {
count--;
if (count <= 0) {
NfcManager.unregisterTagEvent().catch(() => 0);
setDuration(new Date().getTime() - start.getTime());
}
});
return () => {
NfcManager.setEventListener(NfcEvents.DiscoverTag, null);
};
}, [start]);
And, of course, we will have to also render the duration into React
elements.
Before testing it on a real device, we notice one thing. That is, during gameplay, the user cannot see any UI updates because the iOS default scan UI is on top of our Game
component.
In order to provide users some messages while they play the game, we can use NfcManager.setAlertMessageIOS
to update the iOS NFC scan prompt.
React.useEffect(() => {
let count = 5;
NfcManager.setEventListener(NfcEvents.DiscoverTag, (tag) => {
count--;
// show some updates to our player!
NfcManager.setAlertMessageIOS(`${count}...`);
if (count <= 0) {
NfcManager.unregisterTagEvent().catch(() => 0);
setDuration(new Date().getTime() - start.getTime());
}
});
return () => {
NfcManager.setEventListener(NfcEvents.DiscoverTag, null);
};
}, [start]);
Okay, let do a final test on a real device again.
Cool, it works pretty well. By the way, my personal best record is about five seconds. See if you can beat me!
At this point, your App.js
should look like this:
import React from 'react';
import {View, Text, StyleSheet, TouchableOpacity} from 'react-native';
import NfcManager from 'react-native-nfc-manager';
import Game from './Game';
function App(props) {
const [hasNfc, setHasNfc] = React.useState(null);
React.useEffect(() => {
async function checkNfc() {
const supported = await NfcManager.isSupported();
if (supported) {
await NfcManager.start();
}
setHasNfc(supported);
}
checkNfc();
}, []);
if (hasNfc === null) {
return null;
} else if (!hasNfc) {
return (
<View style={styles.wrapper}>
<Text>You device doesn't support NFC</Text>
<TouchableOpacity onPress={() => setModalVisible(true)}>
<Text>Test</Text>
</TouchableOpacity>
</View>
);
}
return <Game />;
}
const styles = StyleSheet.create({
wrapper: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default App;
Here's the Game.js
:
import React from 'react';
import {TouchableOpacity, View, Text, StyleSheet} from 'react-native';
import NfcManager, {NfcEvents} from 'react-native-nfc-manager';
function Game(props) {
const [start, setStart] = React.useState(null);
const [duration, setDuration] = React.useState(0);
React.useEffect(() => {
let count = 5;
NfcManager.setEventListener(NfcEvents.DiscoverTag, (tag) => {
count--;
NfcManager.setAlertMessageIOS(`${count}...`);
if (count <= 0) {
NfcManager.unregisterTagEvent().catch(() => 0);
setDuration(new Date().getTime() - start.getTime());
}
});
return () => {
NfcManager.setEventListener(NfcEvents.DiscoverTag, null);
};
}, [start]);
async function scanTag() {
await NfcManager.registerTagEvent();
setStart(new Date());
setDuration(0);
}
return (
<View style={styles.wrapper}>
<Text>NFC Game</Text>
{duration > 0 && <Text>{duration} ms</Text>}
<TouchableOpacity style={styles.btn} onPress={scanTag}>
<Text>START</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
wrapper: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
btn: {
margin: 15,
padding: 15,
borderRadius: 8,
backgroundColor: '#ccc',
},
});
export default Game;