I've got a minimal test ReactNative test in Expo running. 2 screens, both just have Text within a View.
This is run on a MBP, and tested on my Android device (Note10+) via QR code.
It is not just slow, it's unresponsive most of the time, when it does navigate to a tab on click then it's either instant (rarely) or takes 5-10 seconds. But usually nothing happens.
There are no errors being shown, replacing the same code with a Stack or Drawer works fine.
2 basic screens:
function StackScreenA0({navigation}) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Test stack 0</Text>
<Button title="goto 1" onPress={() => navigation.navigate('Screen A1')} />
</View>
);
}
function StackScreenA1({navigation}) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Test stack 0</Text>
<Button title="goto 0" onPress={() => navigation.navigate('Screen A0')} />
</View>
);
}
App code (minus imports):
const Tab = createBottomTabNavigator();
function App() {
return (
<NavigationContainer>
<Tab.Navigator initialRoutName='Screen A0'>
<Tab.Screen name="Screen A0" component={StackScreenA0} />
<Tab.Screen name="Screen A1" component={StackScreenA1} />
</Tab.Navigator>
</NavigationContainer>
);
}
export default App;
I've never used Expo before but I can't imagine this is normal behaviour but cannot see a reason for it working so badly.
EDIT: I've found other devices to test and tabs are fine on iPad4, Galaxy9, but seriously bad on Note10+.
Rich
Related
i get the 22 images from the react-native-image-filter.
this image are base64 format,
Here is the code on render runction.
render() {
const { images, imageIndex } = this.state;
return (
<View style={styles.container}>
{images?.length > 21 && <View style={{ flex: 1 }}>
<Image resizeMode={'contain'} source={{ uri: `data:image/png;base64,${images[imageIndex]?.base64}` }} style={{ width: 400, flex: 1 }} />
</View>}
<View style={{ height: 140, paddingVertical: 20 }}>
<ScrollView horizontal>
{images?.map((item, index) => {
return (
<View style={[{ marginHorizontal: 10 }, styles.center]}>
<Image
resizeMode={'cover'}
key={index.toString()}
onPress={() => this.setState({ imageIndex: index })}
source={{ uri: `data:image/png;base64,${item.base64}` }}
style={{ width: 80, height: 100, borderRadius: 8 }} />
</View>
)
})}
</ScrollView>
</View>
</View>
);
}
everything is good, but the image loading is too slow.
How can i increate the loading speed? Help me...
The problem is most definitely in Base64-encoded images.
The slowdown is because React Native has to pass quite long Base64 strings back and forth through the Native<->JS bridge, plus native platform has to parse Base64 and convert it into the correct memory representation for the native image view. Many optimizations provided by the platform are therefore missed.
Ideally, all image manipulations should happen on the native platform, and none of the actual image data should even touch JS.
I'd advise to try these two libraries for more performant filter implementations:
https://github.com/GregoryNative/react-native-gl-image-filters
https://github.com/iyegoroff/react-native-image-filter-kit
I have a stack navigator where one of the screen uses a custom header:
import { createStackNavigator } from "#react-navigation/stack";
import * as React from "react";
import { Button, View } from "react-native";
const Stack = createStackNavigator();
function ScreenA({ navigation }) {
return (
<View style={{ flex: 1, justifyContent: "center"}}>
<Button title="Click me" onPress={() => navigation.navigate("ScreenB")} />
</View>
);
}
function ScreenB({ navigation }) {
return (
<View style={{ flex: 1 , justifyContent: "center"}}>
<Button title="Click me" onPress={() => navigation.navigate("ScreenA")} />
</View>
);
}
function TestComp() {
return (
<Stack.Navigator>
<Stack.Screen
name="ScreenA"
component={ScreenA}
options={{ header: () => <View style={{ height: 160, backgroundColor: "red" }}></View> }}
/>
<Stack.Screen name="ScreenB" component={ScreenB} />
</Stack.Navigator>
);
}
export default TestComp;
As a result, the header of ScreenA (a red bar) is visible from ScreenB. This doesn't happen on Android where the header is properly shown ONLY on ScreenA.
How can I stop the header from ScreenA from showing on ScreenB?
Solved it by using <Stack.Navigator headerMode="screen"> !
So, essentially, I have two Views stacked vertically to start and on a button press they need to be animated so the first View scoots to the left and the second View goes up and sits to the right of the first one. Seems easy enough to do with adjusting flex on the first View and changing top and left styling on the second View, looks great on IOS, however on Android this is causing problems with the second View being cut off on the bottom when it goes up (presumably because the View's flex or height isn't tall enough for the content, but unfortunately I can't change that because it messes up flex styling for the rest of the content beneath).
Any suggestions on how to approach this?
This may not be possible, but I'm wondering if there is a way, either with LayoutAnimation or the Animated API, to animate these Views from a flexDirection 'column' to 'row'? That would be crazy convenient.
LayoutAnimation is wonderful, folks! Simply toggle between 'column' and 'row' on state change as desired and LayoutAnimation takes care of the rest—super neat. Here's a simple example for anyone else with the same problem, hope it helps:
export default class Test extends Component {
constructor() {
super();
this.state = {
toggleStyle: 'column',
}
if(Platform.OS === 'android'){
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}
_onPress = () => {
LayoutAnimation.configureNext(CustomLayoutAnimation.position);
let direction = this.state.toggleStyle === 'row' ? 'column' : 'row'
this.setState({toggleStyle: direction})
}
render() {
return(
<View style={{flex: 1}}>
<View style={{flex: 1}} />
<View style={{flex: 1, flexDirection: this.state.toggleStyle}}>
<View style={{flex: 1, width:'100%', backgroundColor: 'blue'}} />
<View style={{flex: 1, width:'100%', backgroundColor: 'green', justifyContent: 'center', alignItems: 'center'}}><Text onPress={() => this._onPress()}>Toggle</Text></View>
</View>
<View style={{flex: 1}} />
</View>
)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
On Android 4.4, ListView separator lines are inconsistent in thickness, and some do not render.
I can't see how this can be a code issue, this is how I render them:
separator: {
height: 1,
backgroundColor: 'grey',
}
...
<ListView
renderSeparator={(sectionID, rowID) =>
<View key={`${sectionID}-${rowID}`} style={styles.separator} />
}
.../>
Here is a screenshot of a View with this problem:
This issue does not happen on iOS or Android 6.
Anyone had this problem before?
Update
I did a test, this is not Android4 issue. It happens on all API version when running on Nexus One device (in android emulator)
I had this issue on iOS and worked around it by adding a hairline margin, like so:
<View
style={{
...styles,
borderWidth: StyleSheet.hairlineWidth,
margin: StyleSheet.hairlineWidth,
}}
>
{// ...row content}
</View>
Just give the height:hairlineWidth in style
I had the same issue and solved changing the view height from a number to StyleSheet.hairlineWidth as some folks said before. Trying to be more visual/specific:
Before:
renderItemSeparator() {
return (
<View style={{ height: .2, backgroundColor: 'rgba(0,0,0,0.3)' }} />
);
}
After:
renderItemSeparator() {
return (
<View style={{ height: StyleSheet.hairlineWidth, backgroundColor: 'rgba(0,0,0,0.3)' }} />
);
}
Actually there is no fix. It's RN "render-canvas-bug".
But I found hack solution.
<ListView
style={Style.listView}
dataSource={data}
renderRow={(data) => this._renderRow(data)}
/>`
Style.listView: {
backgroundColor: '#fff',
}, // or another backgroundColor you need
Then:
_renderRow(goods) {
return (
<View key={'goods_' + goods.id} style={Style.listView_item}>
<TouchableOpacity or View or ...
style={[Style.flex, Style.flexRow, Style.separatorRow, Style.u_paddingVerticalS, Style.u_middle]}
onPress={() => this._xyz(goods)}>
<View>
<AppFont>{goods.name}</AppFont>
</View>
</TouchableOpacity or View or ...>
</View>
);
}
Only important TouchableOpacity style is Style.separatorRow to render your separator. This style should be inside listView_item, where you can use another styles.
listView: {
backgroundColor: '#fff',
},
listView_item: {
paddingHorizontal: em(1.5),
},
flex: {
flex: 1,
},
flexRow: {
flexDirection: 'row',
},
separatorRow: {
marginBottom: 1,
borderBottomWidth: 1,
borderBottomColor: Colors.canvasColor,
},
You can use StyleSheet.hairlineWidth instead of 1 but it's not a must.
I reported it on GitHub
My workaround was to style the containing view and text like this:
const styles = StyleSheet.create({
rowViewContainer: {
flex: 1,
paddingRight: 15,
paddingTop: 13,
paddingBottom: 13,
borderBottomWidth: 0.5,
borderColor: '#c9c9c9',
flexDirection: 'row',
alignItems: 'center',
},
rowText: {
marginLeft: 15,
},
});
This is the ListView:
<ListView
dataSource={this.state.dataSource}
renderRow={(data) => <View style={styles.rowViewContainer}>
<Text style={styles.rowText}>
{data.bb_first_name}
</Text>
</View>}
/>
Looks nice:
This happens because you have empty rows in your data source. You can style your separators to see it
To avoid this just filter your data.
I faced the same issue when trying to render a Divider with a width of 0.5.
It rendered properly on devices with pixel ratio of 2 (e.g. iPhone SE 2nd gen.) but rendered random width on devices with pixel ratio of 3 (e.g. iPhone 12).
As suggested by other answers, using Stylesheet.hairlineWidth fixes the random width issue but the problem was that the width was thinner than 0.5 on devices with pixel ratio of 3.
So this fixed my problem:
import { PixelRatio, View } from 'react-native';
...
export const Divider = () => {
const width = PixelRatio.roundToNearestPixel(0.5);
...
return <View style={{ width }} ... />
}
I am building my first app with React Native, an app with a long list of images. I want to show a spinner instead of image while image is loading. It is sounds trivial but i didn't found a solution.
I think for a spinner i suppose to use ActivityIndicatorIOS , but how am i combining it with an Image component?
<Image source={...}>
<ActivityIndicatorIOS />
</Image>
Is this a right direction? Am i missing something?
I will share my solution
<View>
<Image source={{uri: this.state.avatar}} style={styles.maybeRenderImage}
resizeMode={"contain"} onLoadStart={() => this.setState({loading: true})}
onLoadEnd={() => {
this.setState({loading: false})
}}/>
{this.state.loading && <LoadingView/>}
</View>
LoadingView.js
export default class LoadingView extends Component {
render() {
return (
<View style={styles.container}>
<ActivityIndicator size="small" color="#FFD700"/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
position: "absolute",
left: 0,
right: 0,
top: 0,
bottom: 0,
opacity: 0.7,
backgroundColor: "black",
justifyContent: "center",
alignItems: "center",
}
});
Here is a complete solution to providing a custom image component with a loading activity indicator centered underneath the image:
import React, { Component } from 'react';
import { StyleSheet, View, Image, ActivityIndicator } from 'react-native';
export default class LoadableImage extends Component {
state = {
loading: true
}
render() {
const { url } = this.props
return (
<View style={styles.container}>
<Image
onLoadEnd={this._onLoadEnd}
source={{ uri: url }}
/>
<ActivityIndicator
style={styles.activityIndicator}
animating={this.state.loading}
/>
</View>
)
}
_onLoadEnd = () => {
this.setState({
loading: false
})
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
activityIndicator: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
}
})
I will share my own solution based only on CSS manipulation, which in my opinion is easy to understand, and the code is pretty clean. The solution is a little similar to other answers, but doesn't require absolute position of any component, or creating any additional components.
The idea is to switch between showing an <Image> and <ActivityIndicator>, based on some state variable (isImageLoaded in the snippet below).
<View>
<Image source={...}
onLoad={ () => this.setState({ isImageLoaded: true }) }
style={[styles.image, { display: (this.state.isImageLoaded ? 'flex' : 'none') }]}
/>
<ActivityIndicator
style={{ display: (this.state.isImageLoaded ? 'none' : 'flex') }}
/>
</View>
Also you should set image size using flex property (otherwise image will be invisible):
const styles = StyleSheet.create({
image: {
flex: 1,
}
});
Note that you don't have to initiate the isImageLoaded variable to false in the constructor, because it will have undefined value and the if conditions will act as expected.
Just ran into the same issue. So basically you have the correct approach, but the indicator should of course only be rendered when the image is loading. To keep track of that you need state. To keep it simple we assume you have just on image in the component an keep the state for it in the same component. (The cool kids will argue you should use a higher order component for that and then pass the state in via a prop ;)
The idea then is, that your image starts out loading and onLoadEnd (or onLoad, but then the spinner gets stuck on error, which is fine or course) you re-set the state.
getInitialState: function(){ return { loading: true }}
render: function(){
<Image source={...} onLoadEnd={ ()=>{ this.setState({ loading: false }) }>
<ActivityIndicatorIOS animating={ this.state.loading }/>
</Image>
}
You could also start out with { loading: false } and set it true onLoadStart, but I'm not sure what the benefit would be of that.
Also for styling reasons, depending on your layout, you might need to put the indicator in a container view that is absolutely positioned. But you get the idea.
Yes, deafultSource and loadingIndicatorSource is not working properly. Also image component cannot contain children. Try this solutions => https://stackoverflow.com/a/62510268/11302100
You can simply just add a placeholder
import { Image } from 'react-native-elements';
<Image
style={styles(colors).thumbnail}
source={{ uri: event.image }}
PlaceholderContent={<ActivityIndicator color={colors.indicator} />}
/>
const [imageLoading, setIsImageLoading] = useState(true);
<View>
<View
style={[
{justifyContent: 'center', alignItems: 'center'},
imageLoading ? {display: 'flex'} : {display: 'none'},
]}>
<ActivityIndicator />
</View>
<Image
source={{uri: ""}}
onLoadEnd={() => {setIsImageLoading(false)}}
style={[
imageStyle,
imageLoading ? {display: 'none'} : {display: 'flex'},
]}
/>
</View>