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.
Preview of final output: Let us have a look at how the final output will look like.
Prerequisites & Technolgies Used:
- Introduction to React Native
- Introduction 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 page.
- On pressing the button, it will call the startRecording method. It asks for the permissions of recording the audio in the app.
- Again during recording the audio, it will call the stopRecording method defined. Here we stop the recording and show the modal to ask for the name of recording.
- After that we save a recoding, name 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.
Steps to Create React Native Application:
Step 1: Create the project:
npx create-expo-app voice-notes-app
Step 2: Navigate to the project
cd voice-notes-app
Step 3: Install the packages as follows:
npx expo install expo-av
npx expo install @expo/vector-icons
Project Structure:
The updated dependencies in package.json file will look like:
"dependencies": {
"expo": "~49.0.15",
"expo-status-bar": "~1.6.0",
"react": "18.2.0",
"react-native": "0.72.6",
"expo-av": "~13.4.1",
"@expo/vector-icons": "^13.0.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" ,
},
}); |
Step 4: Navigate to the terminal or command prompt and type the required command there to run the React native application.
npx expo start
- To run on Android:
npx react-native run-android
- To run on IOS:
npx react-native run-ios
Output: