We are going to build a Voice Notes app that will allow us to save our voice as a recording in our application. It is similar to a notes app, but we will have our voice as notes. We can play the voice recordings, record them, and delete them. It leverages React-Native's cross-platform capabilities to create a mobile application that facilitates recording voice and storing it. Using this application, the user will be able to save the audio on his/her device.
To give you a better idea of what we’re going to create, let’s watch a demo video.
Demo Video
Prerequisites & Technologies Used:
- Introduction to React Native
- Introduction to React Native Components
- React Native State
- React Native Props
- Expo CLI
- Node.js and npm (Node Package Manager)
Approach to create Voice Notes App
- The application will have a single page.
- We will create a recording button at the bottom of the page.
- On pressing the button, it will call the startRecording method. It asks for permission to record audio in the app.
- Again, during recording audio, it will call the stopRecording method defined. Here we stop the recording and show the modal to ask for the name of the recording.
- After that, we save a recording, name it in an array.
- Then we will display the list of recordings.
- After that, we can click the play button of each list element, and it will play the audio.
Step-by-Step Implementation
Step 1: Create a React Native Project
Now, create a project with the following command.
npx create-expo-app app-name --template
Note: Replace the app-name with your app name for example : react-native-demo-app
Next, you might be asked to choose a template. Select one based on your preference as shown in the image below. I am selecting the blank template because it will generate a minimal app that is as clean as an empty canvas in JavaScript.

It completes the project creation and displays a message: "Your Project is ready!" as shown in the image below.

Now go into your project folder, i.e., react-native-demo
cd app-nameProject Structure:

Step 2: Run Application
Start the server by using the following command.
npx expo startThen, the application will display a QR code.
For the Android users,
- For the Android Emulator, press " a" as mentioned in the image below.
- For the Physical Device, download the " Expo Go " app from the Play Store. Open the app, and you will see a button labeled " Scan QR Code. " Click that button and scan the QR code; it will automatically build the Android app on your device.
For iOS users, simply scan the QR code using the Camera app.
If you're using a web browser, it will provide a local host link that you can use as mentioned in the image below.

Step 3: Updated Dependencies
The updated dependencies in the package.json file will look like:
"dependencies": {
"expo": "53.0.13",
"expo-status-bar": "~2.2.3",
"react": "18.2.0",
"react-native": "0.79.0",
"expo-av": "~15.1.6",
"@expo/vector-icons": "^14.1.0"
}
Example: In this example, we are following the above-explained approach.
import { Audio } from 'expo-av';
import { StatusBar } from 'expo-status-bar';
import { useEffect, useState } from 'react';
import {
Button, Modal, Pressable, StyleSheet,
Text, TextInput, TouchableOpacity, View
} from 'react-native';
import Ionicons from '@expo/vector-icons/Ionicons';
export default function App() {
const [recordings, setRecordings] = useState([]);
const [recording, setRecording] = useState(null);
const [recordingName, setRecordingName] = useState('');
const [playing, setPlaying] = useState(-1)
const [sound, setSound] = useState(null);
const [isDialogVisible, setDialogVisible] = useState(false);
async function startRecording() {
try {
await Audio.requestPermissionsAsync();
await Audio.setAudioModeAsync({
allowsRecordingIOS: true,
playsInSilentModeIOS: true,
});
let { recording } =
await Audio.Recording
.createAsync(
Audio.RecordingOptionsPresets
.HIGH_QUALITY
);
audio = recording
setRecording(recording);
} catch (err) {
console.error('Failed to start recording', err);
}
}
async function stopRecording() {
await recording.stopAndUnloadAsync();
await Audio.setAudioModeAsync(
{
allowsRecordingIOS: false,
}
);
setDialogVisible(true);
}
const handleSaveRecording = () => {
if (recordingName.trim() !== '') {
setRecordings([
...recordings,
{
name: recordingName,
recording: recording,
},
]);
setRecording(undefined);
setDialogVisible(false);
setRecordingName('');
}
};
useEffect(() => {
return sound
? () => {
sound.unloadAsync();
console.log('Unloaded Sound');
}
: undefined;
}, [sound]);
return (
<View style={styles.container}>
<StatusBar style="auto" />
<Text style={styles.heading}>
Welcome to GeeksforGeeks
</Text>
<Modal visible={isDialogVisible}
animationType="slide"
style={styles.modal}>
<View style={styles.column}>
<Text>
Enter Recording Name:
</Text>
<TextInput
style=
{
{
height: 40,
borderColor: 'gray',
borderWidth: 1,
marginBottom: 10,
padding: 10, width: 200,
borderRadius: 20
}
}
onChangeText={
(text) =>
setRecordingName(text)}
value={recordingName}
/>
<Pressable
style={styles.button}
onPress={handleSaveRecording} >
<Text>Save</Text>
</Pressable>
<Pressable style={styles.button}
onPress={
() =>
setDialogVisible(false)} >
<Text>
Cancel
</Text>
</Pressable>
</View>
</Modal>
<View style={styles.list}>
{recordings.map((recording, index) => {
return (
<View key={index}>
<TouchableOpacity onPress={async () => {
const { sound } =
await recording.
recording.createNewLoadedSoundAsync(
{
isLooping: false,
isMuted: false,
volume: 1.0,
rate: 1.0,
shouldCorrectPitch: true,
},
(status) => {
// console.log(status)
},
false
);
setSound(sound);
setPlaying(index)
await sound.playAsync();
await
sound.setOnPlaybackStatusUpdate(
async (status) => {
if (status.didJustFinish) {
setPlaying(-1)
await sound.unloadAsync();
}
}
);
}} style={styles.playButton}>
<Ionicons
name={playing !== index ?
"play" :
"pause"}
size={30}
color="white" >
<Text
style={styles.recordingName}>
{recording.name}
</Text>
</Ionicons>
<Ionicons name="trash"
size={30}
color="white"
onPress={() => {
setRecordings(recordings
.filter(
(rec, i) =>
i !== index))
}
} />
</TouchableOpacity>
</View>
)
})}
</View>
<View style={
{
flex: 1,
flexDirection: "row",
justifyContent: "center",
alignSelf: "center",
bottom: 100,
position: "absolute",
padding: 10,
}
}>
<Pressable style={styles.button}
onPress={recording ? stopRecording : startRecording}>
<Text style={{
textAlign: "center",
}}>{recording ? 'Stop Recording' : 'Start Recording'}</Text>
</Pressable>
</View>
</View>
);
}
const styles = StyleSheet.create({
row: {
flexDirection: "row",
justifyContent: "space-evenly",
},
container: {
backgroundColor: "#fff",
height: "100%",
marginTop: 50,
}, contentContainer: {
flex: 1,
},
column: {
flex: 1,
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
},
heading: {
color: "green",
fontSize: 30,
textAlign: "center",
fontWeight: "bold",
},
list: {
marginTop: 20,
flex: 1,
flexDirection: "column",
},
modal: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
button: {
alignItems: "center",
backgroundColor: "#DDDDDD",
padding: 10,
marginTop: 10,
borderRadius: 20,
width: 100,
height: 40,
},
recordingName: {
fontSize: 18,
color: "white",
fontWeight: 'bold',
},
playButton: {
backgroundColor: 'gray',
borderRadius: 50,
padding: 10,
margin: 10,
flexDirection: "row",
justifyContent: "space-between",
},
});