Audio is not automatically closing while pressing backbutton in REACT NATIVE application - javascript

In a media player application, I try to use "expo-av" library to build a playlist. everything is working fine. But when I press on the backbutton, it is not behaving properly. I tried in many way. but nothing works for me.
I tried while handling backButton, like, sound.unloadAsync(), sound.stopAsync(), setSound(null).
import React, { useEffect, useState } from 'react';
import {
View,
BackHandler,
Text,
TouchableWithoutFeedback,
StyleSheet,
} from 'react-native';
import * as Progress from 'react-native-progress';
import { connect } from 'react-redux';
import { MaterialCommunityIcons } from '#expo/vector-icons';
import { Audio } from 'expo-av';
const sectionsAllCards = [
{
id: 'audio-01',
name: 'Body scan: Generic under mindfulness',
link: 'Bodyscan.m4a',
}
];
const MusicPlayerList = ({ navigation, route, ...props }) => {
const [isPlaying, setIsPlaying] = useState(false);
const [progress, setProgress] = useState(0);
const [audioIndex, setAudioIndex] = useState(0);
const [soundObject, setSoundObject] = useState(null);
const audioSources = [
require('../../assests/musics/Bodyscan.m4a')
];
const togglePlayback = async () => {
if (isPlaying) await soundObject.pauseAsync();
else await soundObject.playAsync();
setIsPlaying(!isPlaying);
};
const onPlaybackStatusUpdate = (status) => {
setProgress(status.positionMillis / status.durationMillis);
};
useEffect(() => {
const loadAudio = async () => {
const source = audioSources[audioIndex];
const sound = new Audio.Sound();
try {
await sound.loadAsync(source);
setSoundObject(sound);
sound.setOnPlaybackStatusUpdate(onPlaybackStatusUpdate);
} catch (error) {
console.log(error);
}
};
loadAudio();
}, [audioIndex]);
async function handleBackButtonClick() {
navigation.navigate('LoginSignup');
return true;
}
useEffect(() => {
BackHandler.addEventListener(
'hardwareBackPress',
handleBackButtonClick,
);
return () => {
BackHandler.removeEventListener(
'hardwareBackPress',
handleBackButtonClick,
);
};
}, []);
const handleOnPress = async (index) => {
if (index === audioIndex) togglePlayback();
else {
setIsPlaying(false);
setProgress(0);
await soundObject.stopAsync();
setSoundObject(null);
setAudioIndex(index);
}
};
return (
<View style={{ backgroundColor: '#efefef', flex: 1 }}>
{sectionsAllCards.map((card, index) => (
<TouchableWithoutFeedback
key={card.id}
onPress={() => handleOnPress(index)}
>
<View style={styles.boxContainer}>
<Text style={styles.audioText}>{card.name}</Text>
<View style={styles.audioIconContainer}>
{progress >= 0 && progress <= 1 && (
<View>
<Progress.Circle
style={styles.progress}
progress={audioIndex === index ? progress : 0}
indeterminate={false}
showsText={false}
size={60}
borderWidth={2}
color={'#479162'}
/>
<Text
style={{
position: 'absolute',
left: 11,
top: 10,
}}
>
<MaterialCommunityIcons
name={
isPlaying && audioIndex === index
? 'pause'
: 'play'
}
size={38}
style={{ color: '#479162' }}
/>
</Text>
</View>
)}
</View>
</View>
</TouchableWithoutFeedback>
))}
</View>
);
};
const styles = StyleSheet.create({
boxContainer: {
},
audioText: {
},
});
const mapStateToProps = (state) => ({
accessToken: state.auth.accessToken,
});
export default connect(mapStateToProps, {})(MusicPlayerList);

Related

How to hide splash screen once images are loaded

I am using expo and want to show the splash screen until images are all loaded. I am loading images from a url.
What is supposed to happen is when onLoad is called, numberOfImagesLoaded increases by 1, and useAppIsReady is run again. Then there is a check
const imagesAreLoaded = numberOfImagesLoaded > 2;.
When numberOfImagesLoaded > 2, appIsReady is true, then onLayoutRootView gets called and the splash screen gets hidden.
However, I am not sure why when onLoad is called, numberOfImagesLoaded in useAppIsReady does not seem to increase.
App.js
import { StatusBar } from "expo-status-bar";
import { Image, StyleSheet, Text, View } from "react-native";
import { useFonts } from "expo-font";
import * as SplashScreen from "expo-splash-screen";
import React, { useCallback, useState } from "react";
import useAppIsReady from "./useAppIsReady";
import data from "./data";
SplashScreen.preventAutoHideAsync();
export default function App() {
const [numberOfImagesLoaded, setNumberOfImagesLoaded] = useState(0);
const appIsReady = useAppIsReady(numberOfImagesLoaded);
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
await SplashScreen.hideAsync();
}
}, [appIsReady]);
if (!appIsReady) {
return null;
}
return (
<View onLayout={onLayoutRootView} style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<Images setNumberOfImagesLoaded={setNumberOfImagesLoaded} />
<StatusBar style="auto" />
</View>
);
}
const Images = ({ setNumberOfImagesLoaded }) => {
return (
<>
{data.map(({ imgSrc }) => {
return (
<MyImage
key={imgSrc}
imgSrc={imgSrc}
setNumberOfImagesLoaded={setNumberOfImagesLoaded}
/>
);
})}
</>
);
};
const MyImage = ({ imgSrc, setNumberOfImagesLoaded }) => {
const onLoad = () => {
setNumberOfImagesLoaded((prev) => prev + 1);
};
return (
<Image source={{ uri: imgSrc }} style={styles.image} onLoad={onLoad} />
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
image: {
flex: 1,
width: "100%",
},
});
useAppIsReady.js
import { useFonts } from "expo-font";
import * as SplashScreen from "expo-splash-screen";
import React, { useState, useCallback } from "react";
const useAppIsReady = (numberOfImagesLoaded) => {
const [appIsReady, setAppIsReady] = useState(false);
// Load fonts
const baseAssetsPath = "./assets/fonts";
const fonts_by_path = {
"DMSans-Regular": require(`${baseAssetsPath}/DMSans-Regular.ttf`),
"DMSans-Bold": require(`${baseAssetsPath}/DMSans-Bold.ttf`),
"DMSans-BoldItalic": require(`${baseAssetsPath}/DMSans-BoldItalic.ttf`),
"DMSans-Medium": require(`${baseAssetsPath}/DMSans-Medium.ttf`),
"DMSans-MediumItalic": require(`${baseAssetsPath}/DMSans-MediumItalic.ttf`),
"DMSans-Italic": require(`${baseAssetsPath}/DMSans-Italic.ttf`),
};
const [fontsLoaded] = useFonts(fonts_by_path);
// Load images
console.log(numberOfImagesLoaded);
const imagesAreLoaded = numberOfImagesLoaded > 2;
// const imagesAreLoaded = true;
if (fontsLoaded && imagesAreLoaded && !appIsReady) {
setAppIsReady(true);
}
return appIsReady;
};
export default useAppIsReady;
Here is a link to the repo
https://github.com/Bijig0/loading-imgs-testing

React native undefined in not an object evaluation props.route.params.source

Hello I'm a beginner in react native, and I'm trying to get the URI of the video picked from Galerie and use it in another component, I'm testing it with console log but this error is appearing
undefined in not an object evaluation props.route.params.source
this is the page that the video will be picked from Galerie:
import React, { useEffect, useState } from 'react'
import { View, Text, TouchableOpacity, Image } from 'react-native'
import { Camera } from 'expo-camera'
import { Audio } from 'expo-av'
import * as ImagePicker from 'expo-image-picker'
import * as MediaLibrary from 'expo-media-library'
import * as VideoThumbnails from 'expo-video-thumbnails';
import { useIsFocused } from '#react-navigation/core'
import { Feather } from '#expo/vector-icons'
import styles from './styles'
import { useNavigation } from '#react-navigation/native'
export default function CameraScreen() {
const [hasCameraPermissions, setHasCameraPermissions] = useState(false)
const [hasAudioPermissions, setHasAudioPermissions] = useState(false)
const [hasGalleryPermissions, setHasGalleryPermissions] = useState(false)
const [cameraRef, setCameraRef] = useState(null)
const [cameraType, setCameraType] = useState(Camera.Constants.Type.back)
const [cameraFlash, setCameraFlash] = useState(Camera.Constants.FlashMode.off)
const [isCameraReady, setIsCameraReady] = useState(false)
const isFocused = useIsFocused()
const navigation = useNavigation()
useEffect(() => {
(async () => {
const cameraStatus = await Camera.requestCameraPermissionsAsync()
setHasCameraPermissions(cameraStatus.status == 'granted')
const audioStatus = await Audio.requestPermissionsAsync()
setHasAudioPermissions(audioStatus.status == 'granted')
const galleryStatus = await ImagePicker.requestMediaLibraryPermissionsAsync()
setHasGalleryPermissions(galleryStatus.status == 'granted')
})()
}, [])
const recordVideo = async () => {
if (cameraRef) {
try {
const options = { maxDuration: 60, quality: Camera.Constants.VideoQuality['480'] }
const videoRecordPromise = cameraRef.recordAsync(options)
if (videoRecordPromise) {
const data = await videoRecordPromise;
const source = data.uri
/*let sourceThumb = await generateThumbnail(source)*/
/*navigation.navigate('savePost', { source, sourceThumb })*/
}
} catch (error) {
console.warn(error)
}
}
}
const stopVideo = async () => {
if (cameraRef) {
cameraRef.stopRecording()
}
}
const pickFromGallery = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Videos,
allowsEditing: true,
aspect: [16, 9],
quality: 1
})
if (!result.cancelled) {
let sourceThumb = await generateThumbnail(result.uri)
navigation.navigate('AddProduct', {source : result.uri})
}
}
if (!hasCameraPermissions || !hasAudioPermissions || !hasGalleryPermissions) {
return (
<View></View>
)
}
return (
<View style={styles.container}>
{isFocused ?
<Camera
ref={ref => setCameraRef(ref)}
style={styles.camera}
ratio={'16:9'}
type={cameraType}
flashMode={cameraFlash}
onCameraReady={() => setIsCameraReady(true)}
/>
: null}
<View style={styles.sideBarContainer}>
<TouchableOpacity
style={styles.sideBarButton}
onPress={() => setCameraType(cameraType === Camera.Constants.Type.back ? Camera.Constants.Type.front : Camera.Constants.Type.back)}>
<Feather name="refresh-ccw" size={24} color={'white'} />
<Text style={styles.iconText}>Flip</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.sideBarButton}
onPress={() => setCameraFlash(cameraFlash === Camera.Constants.FlashMode.off ? Camera.Constants.FlashMode.torch : Camera.Constants.FlashMode.off)}>
<Feather name="zap" size={24} color={'white'} />
<Text style={styles.iconText}>Flash</Text>
</TouchableOpacity>
</View>
<View style={styles.bottomBarContainer}>
<View style={{ flex: 1 }}></View>
<View style={styles.recordButtonContainer}>
<TouchableOpacity
disabled={!isCameraReady}
onLongPress={() => recordVideo()}
onPressOut={() => stopVideo()}
style={styles.recordButton}
/>
</View>
<View style={{ flex: 1 }}>
<TouchableOpacity
onPress={() => pickFromGallery()}
style={styles.galleryButton}>
</TouchableOpacity>
</View>
</View>
</View>
)
}
and this is the other page where I want to retrieve the video URI:
import { View, Text } from 'react-native'
import React from 'react'
export default function AddProductScreen(props) {
console.log(props.route.prams.source)
return (
<View>
<Text>AddproductScreen</Text>
</View>
)
}
Maybe is a misspelling? you have console.log(props.route.prams.source) instead of console.log(props.route.params.source)

Error querying the api: This works in the web browser but not on mobile. (expo)

I'm new to react native and I'm not able to consume this api, when I start this app in the browser, it works fine, but when I go to the expo app it doesn't display the pokemon image, could someone help me?
import { StatusBar } from 'expo-status-bar';
import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View, Button, Alert, TextInput, Image } from 'react-native';
interface PokeInterface {
sprites : {
back_default : string;
}
}
export default function App() {
const [text, setText] = useState<string>("")
const [response, setResponse] = useState<PokeInterface | any>()
const [image, setImage] = useState<string>()
const handleText = (text : string) => {
setText(text)
}
const searchApi = (pokemonName : string) => {
fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonName}/`, { method: 'GET'})
.then((response) => response.json())
.then((response) => setResponse(response))
}
useEffect(() => {
if(text){
searchApi(text)
}
if(response){
const {sprites} = response
setImage(sprites.front_default)
}
return () => {
if(image){
setImage("")
}
}
},[text, response])
return (
<View style={styles.container}>
<View style={styles.topbar}>
<Text style={styles.title}>Pokedex Mobile</Text>
<TextInput
style={styles.input}
onChangeText={(value: any) => handleText(value)}
value={text}
placeholder="Search Pokemon"
keyboardType="default"
/>
<Text style={styles.text}>{text}</Text>
</View>
{image && (
<Image
style={styles.logo}
source={{uri : `${image}`}}
/>
)}
</View>
);
}
const styles = StyleSheet.create({
text : {
fontSize: 30,
color : "red"
},
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
},
container : {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
title: {
fontSize: 30,
color: '#000'
},
topbar: {
},
logo : {
width: 200,
height: 200
}
});
Your current code causes an infinite loop...
You type some text which triggers searchApi(text)
That writes to response which triggers your effect hook
Because text is still truthy, it triggers searchApi(text) again
Goto #2
As far as I can tell, you can simply discard most of response and just retrieve the image when the text changes.
// this doesn't need to be defined in your component
const getPokemonImage = async (name: string) => {
const res = await fetch(
`https://pokeapi.co/api/v2/pokemon/${encodeURIComponent(pokemonName)}/`
);
if (!res.ok) {
throw new Error(`${res.status}: ${await res.text()}`);
}
return (await res.json<PokeInterface>()).sprites.front_default;
};
export default function App() {
const [text, setText] = useState<string>("");
const [image, setImage] = useState<string>(""); // initial value
const handleText = (text: string) => {
setText(text);
};
useEffect(() => {
if (text) {
getPokemonImage(text)
.then(setImage)
.catch(err => {
console.error("getPokemonImage", err)
// show an error message or something
});
}
}, [ text ]); // only run on text change
import React, {useEffect, useState} from 'react';
import {StyleSheet, Text, View, TextInput, Image, Button} from 'react-native';
// Sorry for removing the types i was using JS
// This code works test it for yourself
export default function App() {
const [text, setText] = useState('');
const [response, setResponse] = useState(); // Here use PokeInterface || any as you are doing conditional checking
const [image, setImage] = useState();
const handleText = e => {
setText(e);
};
const searchApi = pokemonName => {
fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonName}/`, {method: 'GET'})
.then(response => response.json())
.then(response => setResponse(response));
};
useEffect(() => {
if (response) {
const {sprites} = response;
console.log(response);
setImage(sprites.front_default);
}
return () => {
if (image) {
setImage('');
}
};
}, [image, text, response]); // you have to pass all the dependencies so useEffect will be invoked as you didnt pass image dep the useeffct was not invoking
return (
<View style={styles.container}>
<View style={styles.topbar}>
<Text style={styles.title}>Pokedex Mobile</Text>
<TextInput
style={styles.input}
onChangeText={(value: any) => handleText(value)}
value={text}
placeholder="Search Pokemon"
keyboardType="default"
/>
<Text style={styles.text}>{text}</Text>
</View>
{image && <Image style={styles.logo} source={{uri: `${image}`}} />}
<Button
title={'Submit'}
onPress={() => searchApi(text)}
disabled={!text}
/>
</View>
);
}
const styles = StyleSheet.create({
text: {
fontSize: 30,
color: 'red',
},
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
},
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 30,
color: '#000',
},
topbar: {},
logo: {
width: 200,
height: 200,
},
});

React-Native Key Error - All Elements are deleted even though there is a unique key added to the KeyExtractor

I am adding a unique key to the FlatList and in theory only the button i press is supposed to be deleted but instead all elements are deleted
import { render } from "react-dom";
import {
View,
Text,
StyleSheet,
FlatList,
Button,
TouchableOpacity,
} from "react-native";
import { Context } from "../context/BlogContext";
import { Entypo } from "#expo/vector-icons";
const IndexScreen = function () {
const { state, addBlogPost, deleteBlogPost } = useContext(Context);
return (
<View>
<Button title="Add Post" onPress={() => addBlogPost()} />
<FlatList
data={state}
keyExtractor={(blogPosts) => blogPosts.title}
renderItem={({ item }) => {
return (
<View style={styles.row}>
<Text style={styles.title}>
{item.title} - {item.id}
</Text>
<TouchableOpacity
onPress={() => {
deleteBlogPost(item.id);
}}
>
<Entypo style={styles.icon} name="trash" />
</TouchableOpacity>
</View>
);
}}
/>
</View>
);
};
const styles = StyleSheet.create({
row: {
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: 10,
paddingVertical: 20,
borderTopWidth: 1,
borderColor: "gray",
},
title: {
fontSize: 18,
},
icon: {
fontSize: 24,
},
});
export default IndexScreen;
Context Screen:
import createDataContext from "./createDataContext";
const blogReducer = function (state, action) {
switch (action.type) {
case "delete_blogpost":
return state.filter((blogPosts) => {
blogPosts.id !== action.payload;
});
case "add_blogpost":
return [
...state,
{
id: Math.floor(Math.random() * 99999),
title: `Blog Post #${state.length + 1}`,
},
];
default:
return state;
}
};
const addBlogPost = function (dispatch) {
return () => {
dispatch({ type: "add_blogpost" });
};
};
const deleteBlogPost = (dispatch) => {
return (id) => {
dispatch({ type: "delete_blogpost", payload: id });
};
};
export const { Context, Provider } = createDataContext(
blogReducer,
{ addBlogPost, deleteBlogPost },
[]
);
and
export default function (reducer, actions, initialState) {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
// actions === { addBlogPost : (dispatch) => {return () => {}}}
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
}
i dont know how to solve this. all titles are unique as they have different numbers but still all are deleted!!!!!!!!!!!!
here is the pic of the app

Loading spinner as well as fetched products from firebase isn't showing until I refresh the screen in react native

I am using ActivityIndicator for showing the loading screen while my dispatch function dispatches the action and fetches the products from the firebase and renders it on my app screens But this is not happening. My app is showing products that are in store as dummy data and if I refresh the screen then it shows the data from firebase but not the loading spinner to show that loading is true.
ProductOverviewScreen.js:
import React, { useState, useEffect, useCallback } from "react";
import {
FlatList,
View,
Button,
Text,
StyleSheet,
Platform,
ActivityIndicator,
} from "react-native";
import { useSelector, useDispatch } from "react-redux";
import { HeaderButtons, Item } from "react-navigation-header-buttons";
import ProductItem from "../../components/shop/ProductItem";
import * as cartActions from "../../store/actions/cart";
import * as productActions from "../../store/actions/products";
import CustomHeaderButton from "../../components/UI/HeaderButton";
import Colors from "../../constants/Colors";
const ProductOverviewScreen = (props) => {
const [IsLoading, setIsLoading] = useState();
const [IsRefreshing, setIsRefreshing] = useState(false);
const [error, setError] = useState();
const products = useSelector((state) => state.products.availableProducts);
const dispatch = useDispatch();
const loadedProducts = useCallback(() => {
setError(null);
setIsRefreshing(true);
dispatch(productActions.fetchProducts())
.then(setIsLoading(false))
.catch((err) => {
setError(err.message);
});
setIsRefreshing(false);
}, [dispatch, setIsLoading, setError]);
useEffect(() => {
const willFocusSub = props.navigation.addListener(
"willFocus",
loadedProducts
);
return () => {
willFocusSub.remove();
};
}, [loadedProducts]);
useEffect(() => {
const loading = async () => {
setIsLoading(true);
await loadedProducts();
setIsLoading(false);
};
}, [dispatch, loadedProducts]);
const selectItemHandler = (id, title) => {
props.navigation.navigate("ProductDetail", {
productId: id,
productTitle: title,
});
};
const addToCartHandler = async (itemData) => {
setIsLoading(true);
await dispatch(cartActions.addToCart(itemData.item));
setIsLoading(false);
};
if (error) {
return (
<View style={styles.loadingSpiner}>
<Text>An Error occurred! </Text>
<Button
title="Try Again"
onPress={loadedProducts}
color={Colors.primary}
/>
</View>
);
}
if (IsLoading) {
return (
<View style={styles.loadingSpiner}>
<ActivityIndicator size="large" color={Colors.primary} />
</View>
);
}
if (!IsLoading && products.length === 0) {
return (
<View style={styles.loadingSpiner}>
<Text>No Product Found!</Text>
</View>
);
}
return (
<FlatList
data={products}
onRefresh={loadedProducts}
refreshing={IsRefreshing}
renderItem={(itemData) => (
<ProductItem
image={itemData.item.imageUrl}
title={itemData.item.title}
price={itemData.item.price}
onSelect={() => {
selectItemHandler(itemData.item.id, itemData.item.title);
}}
>
<Button
color={Colors.primary}
title="View Details"
onPress={() => {
selectItemHandler(itemData.item.id, itemData.item.title);
}}
/>
{IsLoading ? (
<ActivityIndicator size="small" color={Colors.primary} />
) : (
<Button
color={Colors.primary}
title="To Cart"
onPress={() => {
addToCartHandler(itemData);
}}
/>
)}
</ProductItem>
)}
/>
);
};
ProductOverviewScreen.navigationOptions = (navigationData) => {
return {
headerTitle: "All Products",
headerLeft: () => (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="Menu"
iconName={Platform.OS === "android" ? "md-menu" : "ios-menu"}
color={Platform.OS === "android" ? Colors.primary : "white"}
onPress={() => {
navigationData.navigation.toggleDrawer();
}}
/>
</HeaderButtons>
),
headerRight: () => (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="Cart"
iconName={Platform.OS === "android" ? "md-cart" : "ios-cart"}
onPress={() => {
navigationData.navigation.navigate("Cart");
}}
/>
</HeaderButtons>
),
};
};
const styles = StyleSheet.create({
loadingSpiner: {
flex: 1,
justifyContent: "center",
alignItems: "center",
opacity: 1,
},
});
export default ProductOverviewScreen;
I have also checked on both emulators IOS and android also on my real device. If I open the app on my real device then instantly app renders the data from the firebase but doesn't show a loading spinner.
In useEffect If I try to add dependency loading which costs async code and a function which fetches the data from firebase then it shows an error saying Can't find variable: loading.
Please making loadedProducts from sync to async

Categories