ReactNative ble manager is not reading data from peripheral on IOS - ios

I am building a ReactNative application for both IOS and Android platforms. My app needs to read data from another device via BLE communication. I am using this package for implementing BLE communication, https://github.com/innoveit/react-native-ble-manager. I am having a problem with receiving characteristic data on IOS even though it is working as expected on the Android platform.
I have added the following to the info.plist file:
Privacy - Bluetooth Always Usage Description: App needs to use Bluetooth to receive data from WaterRower machine
I have a component that scan and list down the BLE devices as follow. It is called, BleDeviceList.js
import React, {
useState,
useEffect,
} from 'react';
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
StatusBar,
NativeModules,
NativeEventEmitter,
Button,
Platform,
PermissionsAndroid,
FlatList,
TouchableHighlight,
} from 'react-native';
import {
Colors,
} from 'react-native/Libraries/NewAppScreen';
import BleManager from 'react-native-ble-manager';
const BleManagerModule = NativeModules.BleManager;
const bleManagerEmitter = new NativeEventEmitter(BleManagerModule);
const BleDeviceList = (props) => {
const [isScanning, setIsScanning] = useState(false);
const peripherals = new Map();
const [list, setList] = useState([]);
const [ connectedDevices, setConnectedDevices ] = useState([ ]);
const [ permissionsAllowed, setPermissionsAllowed ] = useState(false)
const startScan = () => {
if (!isScanning) {
BleManager.scan([], 3, true).then((results) => {
console.log('Scanning...');
setIsScanning(true);
}).catch(err => {
console.error(err);
});
}
}
const handleStopScan = () => {
console.log('Scan is stopped');
setIsScanning(false);
}
const handleDisconnectedPeripheral = (data) => {
let peripheral = peripherals.get(data.peripheral);
if (peripheral) {
peripheral.connected = false;
peripherals.set(peripheral.id, peripheral);
setList(Array.from(peripherals.values()));
}
console.log('Disconnected from ' + data.peripheral);
}
const handleUpdateValueForCharacteristic = (data) => {
console.log('Received data from ' + data.peripheral + ' characteristic ' + data.characteristic, data.value);
}
const retrieveConnected = () => {
BleManager.getConnectedPeripherals([]).then((results) => {
if (results.length == 0) {
console.log('No connected peripherals')
}
console.log(results);
for (var i = 0; i < results.length; i++) {
var peripheral = results[i];
peripheral.connected = true;
peripherals.set(peripheral.id, peripheral);
setList(Array.from(peripherals.values()));
}
});
}
const handleDiscoverPeripheral = (peripheral) => {
console.log('Got ble peripheral', peripheral);
if (!peripheral.name) {
peripheral.name = 'NO NAME';
}
peripherals.set(peripheral.id, peripheral);
setList(Array.from(peripherals.values()));
}
const isConnected = (peripheral) => {
return connectedDevices.filter(cd => cd.id == peripheral.id).length > 0;
}
const toggleConnectPeripheral = (peripheral) => {
if (peripheral){
if (isConnected(peripheral)){
BleManager.disconnect(peripheral.id);
setConnectedDevices(connectedDevices.filter(cd => cd.id != peripheral.id))
}else{
BleManager.connect(peripheral.id).then(() => {
let tempConnnectedDevices = [ ...connectedDevices ]
tempConnnectedDevices.push(peripheral);
setConnectedDevices(tempConnnectedDevices);
props.navigation.push('BleRowingSession', { peripheral: peripheral });
let p = peripherals.get(peripheral.id);
if (p) {
p.connected = true;
peripherals.set(peripheral.id, p);
setList(Array.from(peripherals.values()));
props.navigation.push('BleDeviceServiceList', { peripheral: peripheral });
}
console.log('Connected to ' + peripheral.id);
}).catch((error) => {
console.log('Connection error', error);
});
}
}
}
useEffect(() => {
BleManager.start({showAlert: false});
bleManagerEmitter.addListener('BleManagerDiscoverPeripheral', handleDiscoverPeripheral);
bleManagerEmitter.addListener('BleManagerStopScan', handleStopScan );
bleManagerEmitter.addListener('BleManagerDisconnectPeripheral', handleDisconnectedPeripheral );
bleManagerEmitter.addListener('BleManagerDidUpdateValueForCharacteristic', handleUpdateValueForCharacteristic );
if (Platform.OS === 'android' && Platform.Version >= 23) {
PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION).then((result) => {
if (result) {
console.log("Permission is OK");
setPermissionsAllowed(true);
} else {
PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION).then((result) => {
if (result) {
console.log("User accept");
setPermissionsAllowed(true);
} else {
console.log("User refuse");
setPermissionsAllowed(false);
}
});
}
});
} else {
setPermissionsAllowed(true)
}
return (() => {
console.log('unmount');
bleManagerEmitter.removeListener('BleManagerDiscoverPeripheral', handleDiscoverPeripheral);
bleManagerEmitter.removeListener('BleManagerStopScan', handleStopScan );
bleManagerEmitter.removeListener('BleManagerDisconnectPeripheral', handleDisconnectedPeripheral );
bleManagerEmitter.removeListener('BleManagerDidUpdateValueForCharacteristic', handleUpdateValueForCharacteristic );
})
}, []);
const renderConnectButton = (item) => {
if (isConnected(item)) {
return null
}
return (
<Button
title="Connect"
onPress={() => {
toggleConnectPeripheral(item)
}}
/>
)
}
const renderDisconnectButton = (item) => {
if (! isConnected(item)) {
return null
}
return (
<Button
title="Disconnect"
onPress={() => {
toggleConnectPeripheral(item)
}}
/>
)
}
const renderItem = (item) => {
const color = item.connected ? 'green' : '#fff';
return (
<TouchableHighlight>
<View style={[styles.row, {backgroundColor: color}]}>
<Text style={{fontSize: 12, textAlign: 'center', color: '#333333', padding: 10}}>{item.name}</Text>
<Text style={{fontSize: 10, textAlign: 'center', color: '#333333', padding: 2}}>RSSI: {item.rssi}</Text>
<Text style={{fontSize: 8, textAlign: 'center', color: '#333333', padding: 2, paddingBottom: 20}}>{item.id}</Text>
{renderConnectButton(item)}
{renderDisconnectButton(item)}
</View>
</TouchableHighlight>
);
}
const renderContent = () => {
if (! permissionsAllowed) {
return <Text>Bluetooth and locations permissions are required.</Text>
}
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
{global.HermesInternal == null ? null : (
<View style={styles.engine}>
<Text style={styles.footer}>Engine: Hermes</Text>
</View>
)}
<View style={styles.body}>
<View style={{margin: 10}}>
<Button
title={'Scan Bluetooth (' + (isScanning ? 'on' : 'off') + ')'}
onPress={() => startScan() }
/>
</View>
<View style={{margin: 10}}>
<Button title="Retrieve connected peripherals" onPress={() => retrieveConnected() } />
</View>
{(list.length == 0) &&
<View style={{flex:1, margin: 20}}>
<Text style={{textAlign: 'center'}}>No peripherals</Text>
</View>
}
</View>
</ScrollView>
<FlatList
data={list}
renderItem={({ item }) => renderItem(item) }
keyExtractor={item => item.id}
/>
</SafeAreaView>
</>
)
}
return (
renderContent()
);
};
const styles = StyleSheet.create({
scrollView: {
backgroundColor: Colors.lighter,
},
engine: {
position: 'absolute',
right: 0,
},
body: {
backgroundColor: Colors.white,
},
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
color: Colors.black,
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
color: Colors.dark,
},
highlight: {
fontWeight: '700',
},
footer: {
color: Colors.dark,
fontSize: 12,
fontWeight: '600',
padding: 4,
paddingRight: 12,
textAlign: 'right',
},
});
export default BleDeviceList;
As you can see in the code, when the Connect button is clicked, it will redirect the user to another component that reads data from another device. The following is the BleRowingSession.js that reads the data from another device.
import React, { useEffect, useState } from 'react';
import { Text, View, StyleSheet, NativeModules, NativeEventEmitter, ScrollView } from 'react-native';
import BleManager from 'react-native-ble-manager';
const BleManagerModule = NativeModules.BleManager;
const bleManagerEmitter = new NativeEventEmitter(BleManagerModule);
const serviceId = "00001826-0000-1000-8000-00805F9B34FB";
const characteristicId = "00002AD1-0000-1000-8000-00805F9B34FB";
let readDataCache = "";
const BleRowingSession = (props) => {
let peripheral = props.route.params.peripheral;
const [ readData, setReadData ] = useState("");
const setUpBleNotification = () => {
BleManager.retrieveServices(peripheral.id).then((peripheralData) => {
console.log('Retrieved peripheral services', peripheralData);
setTimeout(() => {
BleManager.startNotification(peripheral.id, serviceId, characteristicId).then(() => {
console.log('Started notification on ' + peripheral.id);
bleManagerEmitter.addListener('BleManagerDidUpdateValueForCharacteristic', (data) => {
readDataCache = readDataCache + "\n" + data.value.toString()
setReadData(readDataCache);
});
setTimeout(() => {
}, 500);
}).catch((error) => {
console.log('Notification error', error);
});
}, 500)
});
}
useEffect(() => {
setUpBleNotification()
}, [ ])
return (
<View style={styles.container}>
<ScrollView>
<Text>Ble Rowing Session</Text>
<Text>{readData}</Text>
</ScrollView>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center"
}
})
export default BleRowingSession;
For now, the service id and characteristic id are hardcoded. Of course, I had to choose the right device on the first page that displays the list of BLE devices.
When I run the code, it is working as expected on the Android device, it is receiving the characteristic value. When I run the code on the actual IOS device, it is not receiving any data. But it can scan the devices and connect to them. What is wrong with my code and how can I fix it?

Related

ReactNative : Getting white space between keyboard and comment-box input field

I am getting white space between the keyboard and input box. I have used KeyBoardAvoiding view in my code.
Here is the piece of code:
const [isKeyboardVisible, setKeyboardVisible] = useState(false);
const [offsetValue, setOffsetValue] = useState(0);
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener("keyboardDidShow", () => {
setKeyboardVisible(true);
});
const keyboardDidHideListener = Keyboard.addListener("keyboardDidHide", () => {
setKeyboardVisible(false);
});
if (appState === "active") {
isKeyboardVisible && Keyboard.dismiss();
}
return () => {
keyboardDidHideListener.remove();
keyboardDidShowListener.remove();
};
}, []);
useEffect(() => {
if (isKeyboardVisible) {
setOffsetValue(0);
} else {
setOffsetValue(80);
}
}, [isKeyboardVisible]);
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={Platform.OS === "ios" ? offsetValue : 0}
>
<ScrollView
contentContainerStyle={styles.scrollViewContainer}
alwaysBounceVertical={false}
keyboardShouldPersistTaps={"handled"}
>
<View style={styles.chatInputBoxWrapper}>
<AvyCommentLinearInput
inputRef={props.setInputRef}
value={props.inputValue}
isVideoCaptureEnabled={true}
imageUrl={""}
onSubmit={(value) => props.onChatSubmit(value)}
/>
</View>
</ScrollView>
</KeyboardAvoidingView>
export default StyleSheet.create({
scrollViewStyle: {
marginHorizontal: 16,
borderRadius: 4,
marginBottom: 8,
marginTop: 8,
},
chatInputBoxWrapper: {
...shadowStyle,
flex: 1,
},
scrollViewContainer: { flexGrow: 1 },
});
Video link of the issue I am facing:
https://www.dropbox.com/s/6ydwxhaq06dpfaf/RPReplay_Final1674132226.MP4?dl=0
Any help would be Appreciated!!!
My recommendation - use React Native Avoid SoftInput https://mateusz1913.github.io/react-native-avoid-softinput/
A lot of examples https://github.com/mateusz1913/react-native-avoid-softinput/tree/main/packages/app/src/screens

ReactJS: In App Purchase using in-app-purchase-2 not working

I have:
import { InAppPurchase2 as iap, IAPProduct } from "#ionic-native/in-app-purchase-2";
import { isPlatform } from '#ionic/react';
const IOS: React.FC = () => {
let history = useHistory();
const [productPrice, setPrice] = useState('')
const [productPriceA, setPriceA] = useState('')
const [product, setProduct] = useState([])
const [productA, setProductA] = useState([])
iap.validator = "https://validator.fovea.cc/v1/validate?###";
//initiate initInAppPurchase function
useEffect(() => {
const init = async () => {
await initInAppPurchase();
}
init();
}, []);
//if on an ios or android device, then get product info
const initInAppPurchase = () => {
if ((isPlatform('ios')) || (isPlatform('android'))) {
iap.verbosity = iap.DEBUG;
iap.register([
{
id: "tpmonthly",
alias: "Monthly",
type: iap.PAID_SUBSCRIPTION
}, {
id: "tpannual",
alias: "Annual",
type: iap.PAID_SUBSCRIPTION
},
]);
iap.ready(() => {
let product = iap.get('Monthly');
let productA = iap.get('Annual');
setPrice(product.price)
setProduct(product)
setPriceA(productA.price)
setProductA(productA)
})
iap.refresh();
}
}
//if user clicks purchase button
const purchaseProduct = () => {
if (product.owned) {
alert('A subscription is currently active.')
} else {
iap.order("tpmonthly").then(() => {
iap.when("tpmonthly").approved((p: IAPProduct) => {
p.verify();
p.finish();
});
history.push("/ios-signup/");
})
}
p.refresh();
}
//if user clicks purchase button
const purchaseProductA = () => {
if (product.owned) {
alert('A subscription is currently active.')
} else {
iap.order("tpannual").then(() => {
iap.when("tpannual").approved((p: IAPProduct) => {
p.verify();
p.finish();
});
history.push("/ios-signup/");
})
}
p.refresh();
}
return (
<>
<div className="wrapper" style={{ textAlign: 'center' }}>
<br/><br/>
<Button size="large" style={{ backgroundColor: '#202020', color: '#fff', width: '100%' }} variant="contained" onClick={purchaseProduct}>Subscribe Monthly for {productPrice}</Button><br/><br/>
<Button size="large" style={{ backgroundColor: '#202020', color: '#fff', width: '100%' }} variant="contained" onClick={purchaseProductA}>Subscribe Annually for {productPriceA}</Button><br/><br/>
</div>
</>
);
};
It displays the price of each subscription in the Button and when clicking asks me for my Apple login - but after completing this I get an error in Xcode with :
[store.js] ERROR: ios -> ERROR 6777010: An unknown error occurred - {"productId":"tpmonthly"}
Followed by:
[store.js] state: tpmonthly -> valid
So the subscription does not go through and does not show up in fovea.
Am pulling my hair out - is there anything I can do?

How to stop echo in iOS side react native audio-record-player?

Below my code for playing voice clip I send here from previous screen
import * as Progress from 'react-native-progress';
import { FontFamily, Fonts } from '../../common/GConstants';
import { Image, SafeAreaView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import React, { Component } from 'react';
import { heightPercentageToDP as hp, widthPercentageToDP as wp } from 'react-native-responsive-screen';
import AudioRecorderPlayer from 'react-native-audio-recorder-player';
import Colors from '../../common/GColors';
import GColors from '../../common/GColors';
import KeepAwake from 'react-native-keep-awake';
import WaveForm from 'react-native-audiowaveform';
import images from '../../assets/images/index';
import { secondsToTime } from '../../common/GFunction';
export default class ConfirmRecording extends Component {
constructor() {
super();
this.state = {
recordTime: '',
duration: '',
isPlaying: false,
totalDuration: '',
audioPath: '',
currentPositionSec: '',
currentDurationSec: '',
};
this.audioRecorderPlayer = new AudioRecorderPlayer();
}
showProgress = () => {
if (this.state.currentPositionSec / this.state.totalDuration > 1) {
return Math.round(
this.state.currentPositionSec / this.state.totalDuration,
);
} else {
return Math.fround(
this.state.currentPositionSec / this.state.totalDuration,
);
}
};
onStartPlay = async () => {
console.log('START and is playing', this.state.audioPath + "--------" + this.state.isPlaying);
if (this.state.isPlaying) {
this.setState({ isPlaying: false });
this.audioRecorderPlayer.stopPlayer();
this.audioRecorderPlayer.removePlayBackListener();
} else {
const msg = await this.audioRecorderPlayer.startPlayer(
this.state.audioPath,
// 'http://podcasts.cnn.net/cnn/services/podcasting/specials/audio/2007/05/milesobrien.mp3',
);
console.log('Play MSG', msg);
this.audioRecorderPlayer.addPlayBackListener((e) => {
this.setState({
isPlaying: true,
currentPositionSec: Math.round(
Math.round(Math.round(e.current_position / 10) / 100),
),
currentDurationSec: Math.round(Math.round(e.duration / 10) / 100),
playTime: e.current_position,
duration: e.duration,
});
if (e.duration == e.current_position) {
this.setState({ isPlaying: false });
console.log('Stopped');
this.audioRecorderPlayer.stopPlayer();
this.audioRecorderPlayer.removePlayBackListener();
}
return;
});
}
};
componentDidMount = () => {
var audioPath = this.props.navigation.getParam('audioPath');
var duration = this.props.navigation.getParam('duration');
console.warn("Data from prevciouyas screen", audioPath + "--------" + duration)
this.setState({
audioPath: audioPath,
duration: duration
});
}
componentWillUnmount = () => {
this.audioRecorderPlayer.stopPlayer();
this.audioRecorderPlayer.removePlayBackListener();
this.setState({
audioPath: '',
isPlaying: false
});
}
render() {
return (
<SafeAreaView style={style.mainContainer}>
<View style={style.audioView}>
<Text style={style.audioViewText}>Confirm Recording</Text>
<View style={{ marginTop: hp(2) }}>
<View style={style.secondWaveView}>
<WaveForm
style={style.WaveForm}
source={{ uri: this.state.audioPath }} // not work
stop={this.state.isPlaying}
play={this.state.isPlaying}
// autoPlay={true}
waveFormStyle={{ waveColor: Colors.gray, scrubColor: Colors.darkBlue }}
/>
<Text> {secondsToTime(this.state.currentPositionSec)
.m.toString()
.padStart(2, 0) +
':' +
secondsToTime(this.state.currentPositionSec)
.s.toString()
.padStart(2, 0)}</Text>
</View>
<View style={style.secondAudioView}>
<TouchableOpacity
onPress={(event) => {
this.audioRecorderPlayer.stopPlayer();
this.audioRecorderPlayer.removePlayBackListener();
this.setState({ audioPath: '', isPlaying: false }, () => {
// add Imerssion
// this.props.navigation.state.params.a(true),
this.props.navigation.navigate('ImpressionPro')
});
}
}
activeOpacity={.9}>
<Image source={images.sendIcon} />
</TouchableOpacity>
<View style={{ flex: 1 }} />
<TouchableOpacity style={style.icon}
onPress={() => {
this.audioRecorderPlayer.stopPlayer();
this.audioRecorderPlayer.removePlayBackListener();
this.setState({ audioPath: '', isPlaying: false }, () => {
this.props.navigation.pop(2)
});
}}
activeOpacity={.9}>
<Image source={images.deleteCancelBtn} />
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
this.onStartPlay()
.then(() => {
console.log('Playing');
})
.catch((err) => {
console.log('PErr', err);
});
}}
style={style.icon} activeOpacity={.9}>
<Image source={images.playBtn} />
</TouchableOpacity>
</View>
</View>
</View>
<KeepAwake />
</SafeAreaView>
)
}
}
const style = StyleSheet.create({
mainContainer:
{
flex: 1,
backgroundColor: Colors.darkBlue,
justifyContent: 'center',
alignItems: 'center'
},
secondWaveView:
{
marginTop: hp(2),
marginHorizontal: wp(5.5),
flexDirection: 'row',
justifyContent: 'space-between',
},
secondAudioView:
{
flexDirection: 'row',
marginTop: hp(2),
marginStart: wp(5)
},
WaveForm:
{
height: 25,
flex: 1,
},
icon:
{
marginEnd: wp(5)
},
audioView:
{
backgroundColor: Colors.white,
height: "25%",
width: "88%",
alignSelf: 'center',
borderRadius: hp(2),
},
audioViewText:
{
textAlign: 'center',
marginTop: hp(2),
fontSize: Fonts.fontsize20,
marginHorizontal: wp(6),
fontFamily: FontFamily.medium,
color: Colors.textCoffeeColor
},
})
issue was in the waveform for echo ... both were playing same time so thats why issue generated...

Google SignIn for IOS with React-native (expo)

I'm setting up a page where I can Id with a Google account. This is my code:
import React from "react"
import { StyleSheet, Text, View, Image, Button } from "react-native"
import * as Google from "expo-google-app-auth";
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
signedIn: false,
name: "",
photoUrl: ""
}
}
signIn = async () => {
try {
const result = await Google.logInAsync({
androidClientId:
"1051966217196.apps.googleusercontent.com",
iosClientId:
"1051966217196.apps.googleusercontent.com",
scopes: ["profile", "email"]
})
if (result.type === "success") {
this.setState({
signedIn: true,
name: result.user.name,
photoUrl: result.user.photoUrl
})
} else {
console.log("cancelled")
}
} catch (e) {
console.log("error", e)
}
}
render() {
return (
<View style={styles.container}>
{this.state.signedIn ? (
<LoggedInPage name={this.state.name} photoUrl={this.state.photoUrl} />
) : (
<LoginPage signIn={this.signIn} />
)}
</View>
)
}
}
const LoginPage = props => {
return (
<View>
<Text style={styles.header}>Sign In With Google</Text>
<Button title="Sign in with Google" onPress={() => props.signIn()} />
</View>
)
}
const LoggedInPage = props => {
return (
<View style={styles.container}>
<Text style={styles.header}>Welcome:{props.name}</Text>
<Image style={styles.image} source={{ uri: props.photoUrl }} />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
},
header: {
fontSize: 25
},
image: {
marginTop: 15,
width: 150,
height: 150,
borderColor: "rgba(0,0,0,0.2)",
borderWidth: 3,
borderRadius: 150
}
})
On Android it's ok, everything is fine but in IOS I have that mistake :
Google 400 - That's an error - Error : redirect_uri_mismatch
Where is it goes wrong? I'm new to react native so I need detailed explanations.
I changed the bundle identifier to host.exp.exponent in Google credentials and it works. It's weird because I didn't change it in app.json. But it works.

iOS react-native CameraRoll loads too slowly.

With react-native, I implemented IOS CameraRoll that fetches 300 images from 'Camera Roll' Album on first and keep fetching 300 images whenever scroll reaches the end. Below is My code. SalmonCameraRoll.js
import React from 'react'
import {
View,
Text,
TouchableHighlight,
Modal,
StyleSheet,
Button,
CameraRoll,
Image,
Dimensions,
ScrollView,
FlatList,
} from 'react-native'
import Share from 'react-native-share';
import RNFetchBlob from 'react-native-fetch-blob';
let styles
const { width, height } = Dimensions.get('window')
const fetchAmount = 300;
class SalmonCameraRoll extends React.Component {
static navigationOptions = {
title: 'Salmon Camera Roll',
}
constructor(props) {
super(props);
this.state = {
photos: [],
// index: null,
lastCursor: null,
noMorePhotos: false,
loadingMore: false,
refreshing: false,
};
this.tryGetPhotos = this.tryGetPhotos.bind(this);
this.getPhotos = this.getPhotos.bind(this);
this.appendPhotos = this.appendPhotos.bind(this);
this.renderImage = this.renderImage.bind(this);
this.onEndReached = this.onEndReached.bind(this);
this.getPhotos({first: fetchAmount, assetType: 'Photos'});
}
componentDidMount() {
this.subs = [
this.props.navigation.addListener('didFocus', () => {
this.getPhotos({first: fetchAmount, assetType: 'Photos'});
}),
];
}
componentWillUnmount() {
this.subs.forEach(sub => sub.remove());
}
tryGetPhotos = (fetchParams) => {
if (!this.state.loadingMore) {
this.setState({ loadingMore: true }, () => { this.getPhotos(fetchParams)})
}
}
getPhotos = (fetchParams) => {
if (this.state.lastCursor) {
fetchParams.after = this.state.lastCursor;
}
CameraRoll.getPhotos(fetchParams).then(
r => this.appendPhotos(r)
)
}
appendPhotos = (data) => {
const photos = data.edges;
const nextState = {
loadingMore: false,
};
if (!data.page_info.has_next_page) {
nextState.noMorePhotos = true;
}
if (photos.length > 0) {
nextState.lastCursor = data.page_info.end_cursor;
nextState.photos = this.state.photos.concat(photos);
this.setState(nextState);
}
}
onEndReached = () => {
if (!this.state.noMorePhotos) {
this.tryGetPhotos({first: fetchAmount, assetType: 'Photos'});
}
}
renderImage = (photo, index) => {
return (
<TouchableHighlight
style={{borderTopWidth: 1, borderRightWidth: 1, borderColor: 'white'}}
key={index}
underlayColor='transparent'
onPress={() => {
this.props.navigation.navigate('Camera', { backgroundImageUri: photo.node.image.uri })
}
}
>
<Image
style={{
width: width/3,
height: width/3
}}
representation={'thumbnail'}
source={{uri: photo.node.image.uri}}
/>
</TouchableHighlight>
)
}
render() {
return (
<View style={styles.container}>
<View style={styles.modalContainer}>
<FlatList
numColumns={3}
data={this.state.photos}
initialNumToRender={fetchAmount}
onEndReachedThreshold={500}
onEndReached={this.onEndReached}
refreshing={this.state.refreshing}
onRefresh={() => this.tryGetPhotos({first: fetchAmount, assetType: 'Photos'})}
keyExtractor={(item, index) => index}
renderItem={({ item, index }) => (
this.renderImage(item, index)
)}
/>
</View>
</View>
)
}
}
styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
modalContainer: {
// paddingTop: 20,
flex: 1,
},
scrollView: {
flexWrap: 'wrap',
flexDirection: 'row'
},
shareButton: {
position: 'absolute',
width,
padding: 10,
bottom: 0,
left: 0
}
})
export default SalmonCameraRoll
Problem is that in circumstance of a lot of images(about 10000 images) in 'Camera Roll' album, each image component was loaded so slowly that it was also loaded too slowly when scrolling accordingly.
In other famous apps like Facebook or Instagram, it loads all images quickly at once without fetching whenever scroll reaches end.
How can i make my Image component load fast? Or best of all(if possible), how can i make my CameraRoll load all images quickly at once without fetching whenever scroll reaches end?
Thank you.

Resources