Create a Voice Notes App using React-Native
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:
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.
Javascript
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 w3wiki </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: