Nfc Implementation

Goal

In this tutorial, We will finally add the listener for the NFC tags and using the listener, we will populate our cart.

Libraries and Setup

We will be adding two libraries to achieve our goal. react-native-nfc-manager and react-native-paper.

Make sure to complete the additional setup required for the react-native-nfc-manager library.

Then open the index.js file in your root folder and add some additional configuration for the PaperProvider from react-native-paper to make the file become as shown below:

import React from 'react';
import {AppRegistry} from 'react-native';
import { DefaultTheme, Provider as PaperProvider } from 'react-native-paper';
import App from './App';
import {name as appName} from './app.json';


export default function Main() {

const theme = {
...DefaultTheme,
roundness: 2,
colors: {
...DefaultTheme.colors,
primary: '#000000',
accent: '#FBB8AC',
},
};

return (
<PaperProvider theme={theme}>
<App />
</PaperProvider>
);
}

AppRegistry.registerComponent(appName, () => Main);

Refactoring the code

Refactoring is the process of restructuring existing code. It improves the maintainability and code quality.

To improve the structure of the code, we will make two changes.

  1. We will create a new folder called components and move the CartItem.js file there.
  2. We will create a new file called PreviewProduct.js in the components folder. This will hold the view that will be displayed by our Dialog.

PreviewProduct.js

import React from 'react';
import {Image, StyleSheet, Text, View} from 'react-native';
import {Button} from 'react-native-paper';

const PreviewProduct = ({product, addToCart, discard}) => {

return <View style={{width: '100%', height: '100%'}}>
<Image style={styles.productImage} source={{uri: product.url}}/>

<View style={{flex: 1}}>
<Text style={styles.title}>{product.title}</Text>
<Text style={styles.priceInDollars}>${product.priceInDollars}</Text>
<Text style={styles.description}>{product.description}</Text>
</View>

<View style={styles.buttonContainer}>
<Button mode={'outlined'} style={styles.button} onPress={discard}>Discard</Button>
<Button mode={'outlined'} style={styles.button} onPress={addToCart}>Add to cart</Button>
</View>
</View>;
};

const styles = StyleSheet.create({
productImage: {
height: '70%',
width: '100%',
borderRadius: 4,
backgroundColor: 'pink',
},
button: {
marginHorizontal: 4,
flex: 1,
},
buttonContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
marginTop: 5,
},
priceInDollars: {
fontSize: 19,
color: 'darkgrey',
},
description: {
fontSize: 18,
color: 'darkgrey',
},
});
export default PreviewProduct;

This is a simple view to show the details of the scanned product and enable the user to decide whether to add the scanned product to cart or not.

App.js

Most of our changes will happen here. We will add the nfc listeners and also implement the logic to update the cart.

Styles

Change the content of the styles variable to:

const styles = StyleSheet.create({
container: {
height: '100%',
backgroundColor: 'white',
},
icon: {
height: 30,
width: 30,
},
fabContainer: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: '#FEDBD0',
position: 'absolute',
bottom: 10,
right: 10,
justifyContent: 'center',
},
fabIcon: {
alignSelf: 'center',
},
noItemsContainer: {
height: '100%',
alignSelf: 'center',
justifyContent: 'center',
paddingBottom: 50,
flexDirection: 'column',
display: 'flex',
},
noItemsText: {
textAlign: 'center',
fontSize: 22,
padding: 8,
alignSelf: 'center',

},
directiveText: {
fontSize: 24,
textAlign: 'center',
},
dialogContainer: {
flexDirection: 'row',
alignContent: 'center',
backgroundColor: 'white',
height: '100%',
borderRadius: 4,
},
});

State-Controller variables

We will add two State-Controlled variables to hold the scanned product and the modal visibility state respectively:

const [currentlyScannedProduct, setCurrentlyScannedProduct] = useState(null);
const [modalVisible, setModalVisible] = useState(false);

NFC Related changes

A necessary setup step every time you want to use the NfcManager.

React.useEffect(() => {
NfcManager.start();
}, []);

We will then create a function called readNdef. This function will, as the name connotes, read NFC Data Exchange Format (NDEF) from the NFC tags. NDEF is a lightweight, binary message format used in NFC for data exchange.

function readNdef() {
return new Promise((resolve) => {
let tagFound = null;

NfcManager.setEventListener(NfcEvents.DiscoverTag, (tag) => {
tagFound = tag;
onTagFound(tagFound);
console.log('tag', tag);
resolve(tagFound);
});

NfcManager.setEventListener(NfcEvents.SessionClosed, () => {
if (!tagFound) {
resolve();
}
});

NfcManager.registerTagEvent();
});
}

Another function is the cleanUp one to function, this is to detatch the listeners and unregister the tag event.

const cleanUp = () => {
NfcManager.unregisterTagEvent().catch(() => 0);
NfcManager.setEventListener(NfcEvents.DiscoverTag, null);
NfcManager.setEventListener(NfcEvents.SessionClosed, null);
};

We will also create onShowAddProductModal, addToCart, onTagFound, onDismissAddProductModal and onDiscardScannedItem.

const onShowAddProductModal = () => {
readNdef();
setModalVisible(true);
};
const addToCart = () => {
updateProductsInCart({...productsInCart, [currentlyScannedProduct.id]: currentlyScannedProduct});
onDismissAddProductModal();
};
function onTagFound(tagFound) {
let id = tagFound.id;
let product = allProductsInStore[id];
if (!product) {
console.log('Product not found for this tag');
}
if (productsInCart[id]) {
console.log('Product has been added to cart');
}

setCurrentlyScannedProduct(product);
}
const onDismissAddProductModal = () => {
cleanUp();
setModalVisible(false);
setCurrentlyScannedProduct(null);
};
const onDiscardScannedItem = () => {
setCurrentlyScannedProduct(null);
};

UI Changes

We will also need to update our layout code to reflect these changes and to trigger these methods.

First, we will call the onShowAddProductModal function when the Plus Floating Action Button is pressed.

<TouchableOpacity activeOpacity={0.5} style={styles.fabContainer} onPress={onShowAddProductModal}>
<Image source={require('./assets/icon_plus_fab.png')} style={[styles.icon, styles.fabIcon]}/>
</TouchableOpacity>

We will also add the Preview Dialog using the Portal component from react-native-paper.

<Portal>
<Dialog
style={{height: '80%'}}
dismissable={true}
onDismiss={onDismissAddProductModal}
visible={modalVisible}>
<Dialog.Content
style={styles.dialogContainer}>
{!currentlyScannedProduct &&
<Text style={styles.directiveText}>Scan the NFC tag located on the product</Text>}
{currentlyScannedProduct &&
<PreviewProduct product={currentlyScannedProduct} addToCart={addToCart}
discard={onDiscardScannedItem}/>}
</Dialog.Content>
</Dialog>
</Portal>

Updating the products in store

To simplify this implementation, we will use a unique identifier on the tags to link it to a product in our store. To know which property to use, run the app as you have it now, bring an nfc tag close the the back of the device and watch what is printed on you console. In the readNdef function, we are logging the NDEF content on the tag, this should contain an id, which we will use to link it to a product in our store.

In my test, I have 3 tags whose NDEF content are as follows:

{"id": "0E05B021", "techTypes": ["android.nfc.tech.NfcA", "android.nfc.tech.MifareClassic", "android.nfc.tech.NdefFormatable"]}
{"id": "346FBB810DACC6", "techTypes": ["android.nfc.tech.NfcA"]}
{"id": "34A27F19CE96C6", "techTypes": ["android.nfc.tech.NfcA"]}

I will update the id fields of my store products to match:

const allProductsInStore = {
'346FBB810DACC6': {
id: '346FBB810DACC6',
title: 'Shoulder rolls tee',
description: 'Shoulder rolls tee description',
url: 'https://storage.googleapis.com/material-vignettes.appspot.com/image/34-0.jpg',
priceInDollars: 27,
stock: 8,
quantity: 1,
},
'0E05B021': {
id: '0E05B021',
title: 'Cerise scallop tee',
description: 'Cerise scallop tee description',
url: 'https://storage.googleapis.com/material-vignettes.appspot.com/image/33-0.jpg',
priceInDollars: 42,
stock: 2,
quantity: 1,
},
'34A27F19CE96C6': {
id: '34A27F19CE96C6',
title: 'Grey slouch tank',
description: 'Grey slouch tank description',
url: 'https://storage.googleapis.com/material-vignettes.appspot.com/image/35-0.jpg',
priceInDollars: 24,
stock: 1,
quantity: 1,
},
'3': {
id: '3',
title: 'Sunshirt dress',
description: 'Sunshirt dress description',
url: 'https://storage.googleapis.com/material-vignettes.appspot.com/image/36-0.jpg',
priceInDollars: 58,
stock: 12,
quantity: 1,
}, '4': {
id: '4',
title: 'Fine lines tee',
description: 'Fine lines tee description',
url: 'https://storage.googleapis.com/material-vignettes.appspot.com/image/37-0.jpg',
priceInDollars: 58,
stock: 3,
quantity: 1,
},
};

Now, when you scan that same tag, it should bring up the assigned product details. And voila you have a model Self Service Store to build upon.

Next Steps

We will create an admin application that configures the tags and keeps a track of our inventory. The state of the project after this article can be found here https://github.com/OlaAde/NFCStore/tree/nfc-implementation.

--

--

Adeogo Oladipo

Co-Founder and CTO @DokitariUG. A Strong believer in the Potential in Each Human.