Using react-native-track-player To A Streaming Songs From Napster API - javascript

I managed to build a simple music player using react-native-track-player following a tutorial as part of my learning curve in react native. Now, instead of just playing/streaming songs from provided url tracks in my array of songs, I want to stream the songs from API (though it can beb any API - but I have registered for Napster API) which I feel is not limited by just few songs. But, I can't put together how to implement or call the Napster API to fetch songs.
Please any help/guide I will appreciate so much.
Below is my code:
I have data.js, my array of songs:
const songs = [
{
title: "death bed",
artist: "Powfu",
artwork: require("../assets/album-arts/death-bed.jpg"),
url: "https://github.com/ShivamJoker/sample-songs/raw/master/death%20bed.mp3",
id: "1",
},
{
title: "bad liar",
artist: "Imagine Dragons",
artwork: require("../assets/album-arts/bad-liar.jpg"),
url: "https://github.com/ShivamJoker/sample-songs/raw/master/Bad%20Liar.mp3",
id: "2",
},
{
title: "faded",
artist: "Alan Walker",
artwork: require("../assets/album-arts/faded.jpg"),
url: "https://github.com/ShivamJoker/sample-songs/raw/master/Faded.mp3",
id: "3",
},
];
export default songs;
And here's my playerScreen.js :
import React, {useRef, useEffect, useState} from 'react';
import {
View,
SafeAreaView,
Text,
Image,
FlatList,
Dimensions,
Animated,
StyleSheet,
} from 'react-native';
import TrackPlayer, {
Capability,
useTrackPlayerEvents,
usePlaybackState,
TrackPlayerEvents,
STATE_PLAYING,
Event,
} from 'react-native-track-player';
import songs from './data';
import Controller from './Controller';
import SliderComp from './SliderComp';
const {width, height} = Dimensions.get('window');
// const events = [
// TrackPlayerEvents.PLAYBACK_STATE,
// TrackPlayerEvents.PLAYBACK_ERROR
// ];
export default function PlayerScreen() {
const scrollX = useRef(new Animated.Value(0)).current;
const slider = useRef(null);
const isPlayerReady = useRef(false);
const index = useRef(0);
const [songIndex, setSongIndex] = useState(0);
const isItFromUser = useRef(true);
// for tranlating the album art
const position = useRef(Animated.divide(scrollX, width)).current;
const playbackState = usePlaybackState();
useEffect(() => {
// position.addListener(({ value }) => {
// console.log(value);
// });
scrollX.addListener(({value}) => {
const val = Math.round(value / width);
setSongIndex(val);
});
TrackPlayer.setupPlayer().then(async () => {
// The player is ready to be used
console.log('Player ready');
// add the array of songs in the playlist
await TrackPlayer.reset();
await TrackPlayer.add(songs);
TrackPlayer.play();
isPlayerReady.current = true;
await TrackPlayer.updateOptions({
stopWithApp: false,
alwaysPauseOnInterruption: true,
capabilities: [
Capability.Play,
Capability.Pause,
Capability.SkipToNext,
Capability.SkipToPrevious,
],
});
//add listener on track change
TrackPlayer.addEventListener(Event.PlaybackTrackChanged, async (e) => {
console.log('song ended', e);
const trackId = (await TrackPlayer.getCurrentTrack()) - 1; //get the current id
console.log('track id', trackId, 'index', index.current);
if (trackId !== index.current) {
setSongIndex(trackId);
isItFromUser.current = false;
if (trackId > index.current) {
goNext();
} else {
goPrv();
}
setTimeout(() => {
isItFromUser.current = true;
}, 200);
}
// isPlayerReady.current = true;
});
//monitor intterupt when other apps start playing music
TrackPlayer.addEventListener(Event.RemoteDuck, (e) => {
// console.log(e);
if (e.paused) {
// if pause true we need to pause the music
TrackPlayer.pause();
} else {
TrackPlayer.play();
}
});
});
return () => {
scrollX.removeAllListeners();
TrackPlayer.destroy();
// exitPlayer();
};
}, []);
// change the song when index changes
useEffect(() => {
if (isPlayerReady.current && isItFromUser.current) {
TrackPlayer.skip(songs[songIndex].id)
.then((_) => {
console.log('changed track');
})
.catch((e) => console.log('error in changing track ', e));
}
index.current = songIndex;
}, [songIndex]);
const exitPlayer = async () => {
try {
await TrackPlayer.stop();
} catch (error) {
console.error('exitPlayer', error);
}
};
const goNext = async () => {
slider.current.scrollToOffset({
offset: (index.current + 1) * width,
});
await TrackPlayer.play();
};
const goPrv = async () => {
slider.current.scrollToOffset({
offset: (index.current - 1) * width,
});
await TrackPlayer.play();
};
const renderItem = ({index, item}) => {
return (
<Animated.View
style={{
alignItems: 'center',
width: width,
transform: [
{
translateX: Animated.multiply(
Animated.add(position, -index),
-100,
),
},
],
}}>
<Animated.Image
source={item.artwork}
style={{width: 320, height: 320, borderRadius: 5}}
/>
</Animated.View>
);
};
return (
<SafeAreaView style={styles.container}>
<SafeAreaView style={{height: 320}}>
<Animated.FlatList
ref={slider}
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
data={songs}
renderItem={renderItem}
keyExtractor={(item) => item.id}
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {x: scrollX}}}],
{useNativeDriver: true},
)}
/>
</SafeAreaView>
<View>
<Text style={styles.title}>{songs[songIndex].title}</Text>
<Text style={styles.artist}>{songs[songIndex].artist}</Text>
</View>
<SliderComp />
<Controller onNext={goNext} onPrv={goPrv} />
</SafeAreaView>
);
}

I would pass the songs into your current PlayerScreen through props. You can use a separate component to load tracks from the Napster API and then render a PlayerScreen with those props.
The only part I'm not sure about is what path to pass to the player as the url. The Napster data contains a property previewURL which is an mp3 but it's not the whole song. I believe that the href is the streamable URL. It requires authentication to load the full track though.
The API path that I'm using here is for the most popular tracks.
export default function TopTracks() {
const [songs, setSongs] = useState([]);
useEffect(() => {
const loadData = async () => {
try {
const url = `http://api.napster.com/v2.2/tracks/top?apikey=${API_KEY}&limit=5`;
const res = await axios.get(url);
setSongs(
res.data.tracks.map((track) => ({
duration: track.playbackSeconds,
title: track.name,
artist: track.artistName,
album: track.albumName,
id: track.id,
url: track.href // or track.previewURL?
}))
);
} catch (error) {
console.error(error);
}
};
loadData();
}, []);
return <PlayerScreen songs={songs} />;
}

Related

Why is my Animated.Image not rotating (React-Native)

I'm new to react so please be nice,
I'm trying to animate my compass, so that every time the userLocation is updated, the arrow (in my code the png of the animated image) is rotated at the given angle (here rotation) so that it points at another location. For some reason, it seems like the rotation passed to the Animated.Image remains 0, because the image never rotates. Can someone land me a hand real quick.
Here's my code:
import {
Alert,
Animated,
Easing,
Linking,
StyleSheet,
Text,
View,
} from "react-native";
import React, { useEffect, useRef, useState } from "react";
import * as Location from "expo-location";
import * as geolib from "geolib";
import { COLORS } from "../../assets/Colors/Colors";
export default function DateFinder() {
const [hasForegroundPermissions, setHasForegroundPermissions] =
useState(null);
const [userLocation, setUserLocation] = useState(null);
const [userHeading, setUserHeading] = useState(null);
const [angle, setAngle] = useState(0);
const rotation = useRef(new Animated.Value(0)).current;
useEffect(() => {
const AccessLocation = async () => {
function appSettings() {
console.warn("Open settigs pressed");
if (Platform.OS === "ios") {
Linking.openURL("app-settings:");
} else RNAndroidOpenSettings.appDetailsSettings();
}
const appSettingsALert = () => {
Alert.alert(
"Allow Wassupp to Use your Location",
"Open your app settings to allow Wassupp to access your current position. Without it, you won't be able to use the love compass",
[
{
text: "Cancel",
onPress: () => console.warn("Cancel pressed"),
},
{ text: "Open settings", onPress: appSettings },
]
);
};
const foregroundPermissions =
await Location.requestForegroundPermissionsAsync();
if (
foregroundPermissions.canAskAgain == false ||
foregroundPermissions.status == "denied"
) {
appSettingsALert();
}
setHasForegroundPermissions(foregroundPermissions.status === "granted");
if (foregroundPermissions.status == "granted") {
const location = await Location.watchPositionAsync(
{
accuracy: Location.Accuracy.BestForNavigation,
activityType: Location.ActivityType.Fitness,
distanceInterval: 0,
},
(location) => {
setUserLocation(location);
}
);
const heading = await Location.watchHeadingAsync((heading) => {
setUserHeading(heading.trueHeading);
});
}
};
AccessLocation().catch(console.error);
}, []);
useEffect(() => {
if (userLocation != null) {
setAngle(getBearing() - userHeading);
rotateImage(angle); // Here's the call to the rotateImage function that should cause the value of rotation to be animated
}
}, [userLocation]);
const textPosition = JSON.stringify(userLocation);
const getBearing = () => {
const bearing = geolib.getGreatCircleBearing(
{
latitude: userLocation.coords.latitude,
longitude: userLocation.coords.longitude,
},
{
latitude: 45.47307231766645,
longitude: -73.86611198944459,
}
);
return bearing;
};
const rotateImage = (angle) => {
Animated.timing(rotation, {
toValue: angle,
duration: 1000,
easing: Easing.bounce,
useNativeDriver: true,
}).start();
};
return (
<View style={styles.background}>
<Text>{textPosition}</Text>
<Animated.Image
source={require("../../assets/Compass/Arrow_up.png")}
style={[styles.image, { transform: [{ rotate: `${rotation}deg` }] }]} // this is where it should get rotated but it doesn't for some reason
/>
</View>
);
}
const styles = StyleSheet.create({
background: {
backgroundColor: COLORS.background_Pale,
flex: 1,
// justifyContent: "flex-start",
//alignItems: "center",
},
image: {
flex: 1,
// height: null,
// width: null,
//alignItems: "center",
},
scrollView: {
backgroundColor: COLORS.background_Pale,
},
});
Your error is here
useEffect(() => {
if (userLocation != null) {
setAngle(getBearing() - userHeading);
rotateImage(angle); // Here's the call to the rotateImage function that should cause the value of rotation to be animated
}
}, [userLocation]);
The angle will be updated on the next render, so the rotation you do will always be a render behind. You could either store the result of getBearing and setAngle to that value as well as provide that value to rotateImage:
useEffect(() => {
if (userLocation != null) {
const a = getBearing() -userHeading;
setAngle(a);
rotateImage(a); // Here's the call to the rotateImage function that should cause the value of rotation to be animated
}
}, [userLocation]);
or you could use useEffect and listen for angle changes:
useEffect(() => {
rotateImage(angle)
}, [angle]);

AsyncStorage Android React Native, stuck put in the UseState

i'm sorry for the disturbance,
i'm a trying React Native for the first time ( I'm a Full Stack Engineer React NodeJS ),
i tried by differents tips to put AsyncStorage.getItem inside my state, then display in the map,
but everytime, "Error map undefined", but if i put the value inside my State Array, it's working,
i tried with JSON Stringify, JSON Parse... Like in WEB,
but not working...
Here is my code :
import { useEffect, useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import RadioForm from 'react-native-simple-radio-button';
import AsyncStorage from '#react-native-async-storage/async-storage';
const SelectOption = () => {
const [value, setValue] = useState([]);
const saveOption = (item) => {
try {
setValue([...value, {name: item, id: Math.random()}]);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
AsyncStorage.setItem('option', JSON.stringify(value));
}, [value]);
// Put GetItem in the state
useEffect(() => {
const getOption = async () => {
try {
const jsonValue = await AsyncStorage.getItem('option');
if (jsonValue !== null) {
setValue(JSON.parse(jsonValue));
}
} catch (e) {
console.log(e);
}
};
getOption();
}, []);
AsyncStorage.getItem('option').then((value) => {
console.log(value);
});
const radioProps = [
{label: 'Option 1', value: 'option1'},
{label: 'Option 2', value: 'option2'},
{label: 'Option 3', value: 'option3'}
];
return (
<View style={styles.sectionContainer}>
<RadioForm
radio_props={radioProps}
initial={0}
onPress={(value) => {
saveOption(value);
}}
/>
{value.map((item) => {
return <Text key={item.id}>{item.name}</Text>;
})
}
</View>
);
};
const styles = StyleSheet.create({
sectionContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default SelectOption;
Thanks :pray:
i tried with JSON Stringify, JSON Parse... Like in WEB,
You wants to set value in local storage so, don't need to set the value in useEffect hook. Just add in your saveOption function like
const saveOption = (item) => {
try {
let newValue = [...value, {name: item, id: Math.random()}]
AsyncStorage.setItem('option', newValue);
setValue(newValue);
} catch (e) {
console.log(e);
}
};
And make a getOption async function like
const getOption = async () => {
try {
const jsonValue = await AsyncStorage.getItem('option');
if (jsonValue) {
await setValue(jsonValue);
}
} catch (e) {
console.log(e);
}
};
Then render in JSX like
{value.map((item) => {
return <Text key={item.id}>{item.name}</Text>;
}

Fetch unread messages from PubNub and render notifications

I am trying to get unread messages from PubNub. The process is like this:
Set the grant
Get channel metadata
get unread messages etc...
I am not having the luck running these functions in order. That one is executed after the previous one is done.
async function setGrant(){
await pubnub.grant(
{
channels: [userId.toString() + '.*'],
ttl: 55,
read: true,
write: true,
update: true,
get: true,
},(status) => {
return new Promise(function (resolve) {
resolve(status)
});
});
}
async function getMetadata() {
await pubnub.objects.getAllChannelMetadata(
{
include: {
customFields: true
}
}).then((res: any) => {
return new Promise(function (resolve) {
resolve(setChannelMetadata(res.data));
});
});
}
async function getUnreadedMessages() {
await pubnub.messageCounts({
channels: channels,
channelTimetokens: tokens,
}).then((res: any) => {
console.log(res);
return new Promise(function (resolve) {
resolve(setUnreadMessage(res));
});
});
}
async function setChannelsAndTokens(){
channelMetadata.forEach((value, index) => {
tokens.push(value.custom.lastToken);
channels.push(value.id)
});
return new Promise(function (resolve) {
getUnreadedMessages()
resolve("Chanells and tokens set!");
})
}
function getUnreadMessagesProccess() {
return setGrant().then(getMetadata).then(setChannelsAndTokens).then(getUnreadedMessages)
}
EDIT:
Looks like this is the problem with rendering too. When I get getUnreadedMessages()
then I am editing objects clients:
clients.forEach((value, index) => {
if (res.channels[value.id + '-' + userId + '-chat']) {
value.unreadMessages = res["channels"][value.id + '-' + userId + '-chat'];
} else {
value.unreadMessages = 0;
}
console.log("UNREAD MESSAGE", value.unreadMessages, value.username);
});
based on this I am setting showIcon:
<ClientItem
client={item.item}
isAdmin={isAdmin}
navigation={navigation}
fromAdminTab={fromAdminTab}
showIcon={item.item.unreadMessages>0}
/>
But this icon is not showing corretly.
ClientList:
import Colors from '#helper/Colors';
import { useSelector } from '#redux/index';
import { HealthierLifeOption } from '#redux/types';
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, ActivityIndicator } from 'react-native';
import ClientItem from './ClientItem';
import {usePubNub} from "pubnub-react";
export type Client = {
id: number;
username: string;
individualPlanPaid: boolean;
healthierLifeOption?: HealthierLifeOption;
company?: {
id: number;
companyName: string;
};
mentor?: {
id: number;
username: string;
};
}
type Props = {
navigation: any;
clients: Client[];
isAdmin?: boolean;
isLoading: boolean;
onEndReached: Function;
fromAdminTab?: boolean
}
const ClientList = ({ navigation, clients, onEndReached, isLoading, isAdmin = false, fromAdminTab= false }: Props) => {
const resultCount = useSelector(state => state.user.menteesResultCount);
const [hasMoreResults, setHasMoreResults] = useState(true);
const userId = useSelector(state => state.user.id);
const pubnub = usePubNub();
useEffect(() => {
getUnreadMessagesProccess
setHasMoreResults(resultCount !== clients?.length);
}, [resultCount, clients]);
async function setGrant() {
return new Promise( async resolve => {
await pubnub.grant({
channels: [
'100-68-chat',
'101-68-chat',
'22-2-chat',
'22-96-chat',
'73-68-chat',
'78-68-chat',
'79-68-chat',
'81-68-chat',
'90-68-chat',
'92-68-chat',
'93-68-chat',
'98-68-chat',
'99-68-chat' ],
ttl: 55,
read: true,
write: true,
update: true,
get: true,
}, response => resolve(response));
});
}
async function getMetadata() {
const options = {include: {customFields: true}};
return await pubnub.objects.getAllChannelMetadata(options);
}
function setChannelsAndTokens(channelMetadata, channels, tokens) {
channelMetadata.data.forEach((value, index) => {
tokens.push(value.custom.lastToken);
channels.push(value.id.toString())
});
}
async function getUnreadedMessages(channels, tokens) {
return new Promise(async resolve => {
pubnub.messageCounts({
channels: null,
channelTimetokens: tokens,
}).then(res => {
clients.forEach((value, index) => {
if (res.channels[value.id + '-' + userId + '-chat']) {
value.unreadMessages = res["channels"][value.id + '-' + userId + '-chat'];
} else {
value.unreadMessages = 0;
}
console.log("UNREAD MESSAGE", value.unreadMessages, value.username);
});
resolve()
})
.catch(error => resolve(error));
});
}
async function getUnreadMessagesProccess() {
const tokens = ['1000'];
const channels = ['1234567'];
const userId = 1234567;
const auth = await setGrant(userId);
log([{statusCode: auth.statusCode}]);
const channelMetadata = await getMetadata();
log([channelMetadata,channels,tokens]);
setChannelsAndTokens(channelMetadata, channels, tokens);
const unread = await getUnreadedMessages(channels, tokens);
log([unread]);
return unread;
}
return (
<View>
<FlatList
keyExtractor={item => item.id.toString()}
data={clients}
onEndReached={() => hasMoreResults ? onEndReached() : null}
onEndReachedThreshold={0.4}
renderItem={item => (
<ClientItem
client={item.item}
isAdmin={isAdmin}
navigation={navigation}
fromAdminTab={fromAdminTab}
showIcon={parseInt(item.item.unreadMessages) > 0 }
/>
)}
ListFooterComponent={isLoading
? () => (
<View style={{ padding: 20 }}>
<ActivityIndicator animating size="large" color={Colors.border_gray} />
</View>
)
: null
}
/>
</View>
);
}
export default ClientList;
ClientItem:
import React, {useEffect, useState} from 'react';
import { Text, View, Image, TouchableOpacity } from 'react-native';
import Images from '#helper/Images';
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
import Colors from '#helper/Colors';
import styles from '../styles';
import ClientModal from './ClientModal/ClientModal';
import { Client } from './ClientList';
import { HealthierLifeOption } from '#redux/types';
type Props = {
client: Client;
navigation: any;
isAdmin?: boolean;
fromAdminTab?: boolean;
showIcon?: boolean
}
const ClientItem = ({ client, navigation, isAdmin = false, fromAdminTab= false, showIcon= false }: Props) => {
const [showModal, setShowModal] = useState(false);
console.log(showIcon); // its not passed correclty
let clientIcon = Images.icon.logoIconOrange
const handleContinueButton = () => {
if(!fromAdminTab) {
navigation.navigate('ChatFromClientsList', { selectedId: client.id, showBackButton: true });
}else {
setShowModal(!showModal)
}
};
return (
<View style={styles.client_item_container}>
<TouchableOpacity style={styles.client_content_container} onPress={handleContinueButton}
>
<View style={styles.image_container}>
<Image
style={styles.client_profile_img}
source={clientIcon}
resizeMode="contain"
resizeMethod="resize"
/>
</View>
<View style={styles.title_container}>
<Text style={styles.title} >{ client.username } </Text>
</View>
<View style={styles.dot_container}>
{showIcon && <FontAwesome5
name="comment-dots"
size={20}
color={Colors.red}
/> }
</View>
<View style={styles.hamburger_container} >
<FontAwesome5
name="bars"
size={30}
color={Colors.black}
onPress={() => setShowModal(!showModal)}
/>
</View>
<View>
<ClientModal
isVisible={showModal}
onCollapse={(value: boolean) => setShowModal(value)}
closeModal={(value: boolean) => setShowModal(value)}
client={client}
navigation={navigation}
isAdmin={isAdmin}
/>
</View>
</TouchableOpacity>
</View>
);
};
export default ClientItem;
I am not sure if it is because of fethichng data from Pubnub or because of rendering or how i process data in foreach.
Run function after previous one is done
Using async and await in an empirical coding style. This helps organize the code. Making the code easy to read. This helps also for order of operations.
What we did was add a return in front of the await and promise in each function. That allows us to assign result variables after each function is finished executing while preserving order of execution as the code is written.
💡 Note: when the SDK is initialized with the secretKey, there is no need to grant yourself access to resources. Simply use any method as if you already had permissions. The SDK will automatically sign all requests using the your secret key.
Test this by pressing the Run code snippet button below the code snippet.
(async ()=>{
'use strict';
const publishKey = "pub-c-51f1008b-7ddf-42b7-aec9-2d0153b0e766";
const subscribeKey = "sub-c-1597b696-05af-11e6-a6dc-02ee2ddab7fe";
const secretKey = "sec-c-YTVjYjUzMWMtM2MxZC00YzdiLWE0ZjAtNWRmMWVmYmNkZGNl";
const myUUID = "myUniqueUUID"
const pubnub = new PubNub({
publishKey : publishKey,
subscribeKey : subscribeKey,
secretKey : secretKey,
uuid : myUUID,
});
const unread = await getUnreadMessagesProccess();
async function getUnreadMessagesProccess() {
const tokens = ['1000']; // placeholder change me
const channels = ['1234567']; // placeholder change me
const userId = 1234567; // placeholder change me
const auth = await setGrant(userId);
log([{statusCode: auth.statusCode}]);
const channelMetadata = await getMetadata();
log([channelMetadata,channels,tokens]);
setChannelsAndTokens(channelMetadata, channels, tokens);
const unread = await getUnreadedMessages(channels, tokens);
log([unread]);
return unread;
}
async function setGrant(userId) {
return new Promise( async resolve => {
await pubnub.grant({
channels: [userId.toString() + '.*'],
ttl: 55,
read: true,
write: true,
update: true,
get: true,
}, response => resolve(response));
});
}
async function getMetadata() {
const options = {include: {customFields: true}};
return await pubnub.objects.getAllChannelMetadata(options);
}
function setChannelsAndTokens(channelMetadata, channels, tokens) {
channelMetadata.data.forEach((value, index) => {
tokens.push(value.custom.lastToken);
channels.push(value.id)
});
}
async function getUnreadedMessages(channels, tokens) {
try {
return await pubnub.messageCounts({
channels: channels,
channelTimetokens: tokens,
});
}
catch(error) {
return error;
}
}
async function getUnreadedMessagesAlternative(channels, tokens) {
return new Promise(async resolve => {
pubnub.messageCounts({
channels: channels,
channelTimetokens: tokens,
}).then(result => resolve(result))
.catch(error => resolve(error));
});
}
function log(data) {
// console.log(data);
document.querySelector("#out").innerHTML +=
`<div>${JSON.stringify(data)}</div>`;
}
})();
<div id="out"></div>
<script src="https://cdn.pubnub.com/sdk/javascript/pubnub.4.33.0.js"></script>

Translate gendre_ids from TMDB API in react-native application

I'm wondering how to show a genre to each movie in the list. So I have already other details like title, poster_path or description.
The problem comes when I'm trying to show a genre's becouse they are a numbers and I don't know how to translate them to string like 'Horror'
Here is code for fetch data:
fetch(
`https://api.themoviedb.org/3/search/movie?&api_key=${
this.apiKey
}&query=${searchTerm}`,
)
.then(data => data.json())
.then(data => {
const results = data.results;
const movieRows = [];
const movieGen = [];
results.forEach(movie => {
movie.poster_path =
'https://image.tmdb.org/t/p/w500' + movie.poster_path;
const movies = <MovieRow key={movie.id} movie={movie} />;
movieRows.push(movies);
});
this.setState({rows: movieRows});
});
}
and also diplay it in custom component like movie card:
viewMore = () => {
Alert.alert(
`PRODUCTION : ${this.props.movie.original_language}`,
`DESCRIPTION : ${this.props.movie.overview}\n \n GENRE : ${
this.props.movie.genre_ids
}`,
);
};
render() {
return (
<View
style={{
width: '100%',
alignItems: 'center',
justifyContent: 'center',
}}>
<CardCustom
title={this.props.movie.title}
popularity={this.props.movie.popularity}
vote_count={this.props.movie.vote_count}
poster_path={this.props.movie.poster_path}
onPress={this.viewMore}
/>
</View>
);
}
}
export default MovieRow;
This how this looks like in the application:
and the response from api for genre_ids looks like that
I noticed that I have to use separate API for genre's. Now I want to match them to current movie and I dont know how to do it.
Here is a code
class MovieRow extends Component {
constructor() {
super();
this.apiKey = '1bd87bc8f44f05134b3cff209a473d2e';
this.state = {};
}
viewMore = () => {
Alert.alert(
`PRODUCTION : ${this.props.movie.original_language}`,
`DESCRIPTION : ${this.props.movie.overview}\n \n
GENRE : ${this.props.movie.genre_ids}`, // < ------ NUMBER DISPLAYS. HOW TO MATCH GENRE WITH CURRENT MOVIE?
);
this.fetchGenre();
};
fetchGenre() {
fetch(
`https://api.themoviedb.org/3/genre/movie/list?&api_key=${this.apiKey}`,
)
.then(data => data.json())
.then(data => {
const resultGenres = data.genres;
const genreRow = [];
console.log(resultGenres);
resultGenres.map(genre => {
console.log('name', genre.name, 'id', genre.id);
const genres = <Text>genre: {genre.name}</Text>;
genreRow.push(genres);
});
this.setState({gen: genreRow});
});
}
render() {
return (
<View
style={{
width: '100%',
alignItems: 'center',
justifyContent: 'center',
}}>
<CardCustom
title={this.props.movie.title}
popularity={this.props.movie.popularity}
vote_count={this.props.movie.vote_count}
poster_path={this.props.movie.poster_path}
onPress={this.viewMore}
/>
{this.state.gen}
</View>
);
}
}
also this is how response looks like
Regards
Just get an array with all ids of genders and store it into your state, then when you want to display it you will just use a map. like so :
this.state.gender_ids = [
1: "Action",
2: "Horror",
3: "Other gender"
]
this.props.movie.genre_ids.map(id => <Text key={this.state.gender_ids[id]}>{this.state.gender_ids[id]}</Text>)
Just run the following code into your browser's console, I'm pretty sure from now on you'll get the job done.
Example for pairing :
let gendersFromServer = [
{
id: 28,
name: "Action"
},
{
id: 12,
name: "Adventure"
},
{
id: 16,
name: "Animation"
},
// other genders here
]
let gender_ids = [] // intialize with an empty array
gendersFromServer.map(el => gender_ids[el.id] = el.name) // here you transform the data
// here you can setState({gender_ids})
const movie = {
gender_ids: [
28,
12,
16
]
// rest of data
}
// how to get text gender, notice that gender_ids from console log is the one you use in state, not the one from the movie
movie.gender_ids.map(id => console.log(gender_ids[id]))
EDIT 2:
Hope this will solve your problem finally
import React from 'react'
import { SafeAreaView, ScrollView, View, Text } from 'react-native'
const API_KEY = '1bd87bc8f44f05134b3cff209a473d2e'
export default props => {
const [genres, setGenres] = React.useState([])
const [movies, setMovies] = React.useState([])
React.useEffect(() => {
fetch('https://api.themoviedb.org/3/search/movie?&query=Now+You+See+Me&api_key=' + API_KEY)
.then(res => res.json())
.then(result => {
setMovies(result.results)
})
fetch('https://api.themoviedb.org/3/genre/movie/list?&api_key=' + API_KEY)
.then(genre => genre.json())
.then(result => {
const genres = result.genres.reduce((genres,gen) => {
const { id, name } = gen
genres[id] = name
return genres
},[])
setGenres(genres)
})
},[])
const Movies = () => movies.map(movie => {
return (
<View>
<Text>{movie.title}</Text>
<View>
<Text>Genres :</Text>
{
movie.genre_ids.map(id => {
return <Text>{genres[id]}</Text>
})
}
</View>
</View>
)
})
return (
<SafeAreaView style={{flex: 1}}>
<ScrollView style={{flex: 1}}>
<Text>Movies here</Text>
<Movies />
</ScrollView>
</SafeAreaView>
)
}

Why is props un changed by map state to props in React Redux

I have updated my store by using mapDispatchToProps like so:
function mapDispatchToProps(dispatch){
return{
addFirstImageUrl: (firstUrl) => dispatch({
type: "ADD_FIRST_IMAGE_URL",
firstUrl
})
}
}
I know that this works because when I run mapStateToProps I log the state like so:
function mapStateToProps(state){
console.log(state)
return{
firstImageUrl: state.firstImageUrl
}
}
This returns:
}
Object {
"posts": Object {
"firstImageTitle": "",
"firstImageUrl": "https://firebasestorage.MYURL",
"secondImageTitle": "",
"secondImageUrl": "",
},
}
however when I call this.props.firstImageUrl it returns undefined. I feel like this should return the url above, is this thinking wrong?
component function:
uploadImage = async (uri) => {
const response = await fetch(uri);
const blob = await response.blob();
var ref = firebase
.storage()
.ref()
.child(new Date().toString());
const snapshot = await ref.put(blob)
const url = await snapshot.ref.getDownloadURL();
this.props.addFirstImageUrl(url);
console.log("First Image Url: " + this.props.firstImageUrl)
};
import React from "react";
import {
StyleSheet,
Text,
View,
TouchableOpacity
} from "react-native";
import { Camera } from "expo-camera";
import * as Permissions from "expo-permissions";
import { FontAwesome } from "#expo/vector-icons";
import * as firebase from "firebase";
import { connect } from "react-redux";
import { addFirstImageUrl } from "../store/actions/posts";
import { bindActionCreators } from "redux";
function mapStateToProps(state){
console.log(state)
return{
firstImageUrl: state.firstImageUrl
}
}
function mapDispatchToProps(dispatch){
return{
addFirstImageUrl: (firstUrl) => dispatch({
type: "ADD_FIRST_IMAGE_URL",
firstUrl
})
}
}
class FirstCameraScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
hasCameraPermissions: null,
type: Camera.Constants.Type.back,
isFlashLIghtOn: Camera.Constants.FlashMode.off,
firstImageUrl: " "
};
}
static navigationOptions = {
title: "FirstCamera"
};
//ask for camera permission
async componentDidMount() {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({
hasCameraPermissions: status === "granted"
});
}
// Flip the camera
flipCamera = () => {
this.setState({
type:
this.state.type === Camera.Constants.Type.back
? Camera.Constants.Type.front
: Camera.Constants.Type.back
});
};
//Toggle Flashlight
flashLight = () => {
this.setState({
isFlashLIghtOn:
this.state.isFlashLIghtOn === Camera.Constants.FlashMode.off
? Camera.Constants.FlashMode.on
: Camera.Constants.FlashMode.off
});
};
//Take Picture and send to home
takePicture = async () => {
if (this.camera) {
let photo = await this.camera.takePictureAsync();
if (!photo.cancelled) {
await this.uploadImage(photo.uri);
}
this.props.navigation.navigate("FirstNamingScreen");
}
};
uploadImage = async (uri) => {
const response = await fetch(uri);
const blob = await response.blob();
var ref = firebase
.storage()
.ref()
.child(new Date().toString());
const snapshot = await ref.put(blob)
const url = await snapshot.ref.getDownloadURL();
this.props.addFirstImageUrl(url);
console.log("First Image Url: " + props.posts)
};
render() {
const { hasCameraPermissions } = this.state;
const { navigate } = this.props.navigation;
if (hasCameraPermissions === null) {
return <View />;
} else if (hasCameraPermissions === false) {
return (
<View>
<Text>No access to Camera</Text>
</View>
);
} else if (hasCameraPermissions === true) {
return (
<View style={styles.container}>
<Camera
style={styles.cameraView}
type={this.state.type}
flashMode={this.state.isFlashLIghtOn}
ref={ref => {
this.camera = ref;
}}
>
<View style={styles.actionContainer}>
<TouchableOpacity
onPress={() => this.flipCamera()}
style={styles.iconHolder}
>
<FontAwesome name="camera" size={35} style={styles.icon} />
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
this.takePicture();
}}
style={styles.iconHolder}
>
<FontAwesome name="circle" size={35} style={styles.icon} />
</TouchableOpacity>
<TouchableOpacity
onPress={() => this.flashLight()}
style={styles.iconHolder}
>
<FontAwesome name="flash" size={35} style={styles.icon} />
</TouchableOpacity>
</View>
</Camera>
</View>
);
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(FirstCameraScreen)
Change the mapStateToProps to this.
function mapStateToProps(state) {
console.log(state);
return {
firstImageUrl: state.posts.firstImageUrl
};
}
And your uploadImage method.
uploadImage() {
const response = await fetch(uri);
const blob = await response.blob();
var ref = firebase
.storage()
.ref()
.child(new Date().toString());
const snapshot = await ref.put(blob);
const url = await snapshot.ref.getDownloadURL();
this.props.addFirstImageUrl(url);
console.log("First Image Url: " + this.props.firstImageUrl);
};
constructor
constructor(props) {
super(props);
this.uploadImage = this.uploadImage.bind(this);
this.state = {
hasCameraPermissions: null,
type: Camera.Constants.Type.back,
isFlashLIghtOn: Camera.Constants.FlashMode.off,
firstImageUrl: " "
};
}

Categories