Inverting a FlatList - android

I'm currently working on a react native app for android, and have access to the new FlatList. It has some very needed features. However, my listview is inverted using the react-native-invertable-scroll-view. This does not seem to work properly with FlatList. The scrolling is not properly reversed.
Does anyone know how to invert a FlatList? Or perhaps how to load a Flatlist, and then force a scroll to the bottom without the user noticing it? My own attempts to force a scroll down are often delayed.

This now comes out of the box in FlatList!
Just use:
<FlatList
inverted
data=...
renderItem=...
/>
This causes the first item to appear at the bottom of the list, and the list starts at the bottom, showing the first item.
If you need the last item to show at the bottom, just reverse the array that you are providing in the data prop.

render() {
const messages = this.props.messages.reverse();
return (
<FlatList
renderItem={this._renderItem}
data={ messages }
keyExtractor={this._keyExtractor}
style={{ transform: [{ scaleY: -1 }] }}
/>
); }
_keyExtractor = (item, index) => item.id+''+index;
_renderItem = ({item}) => (
<View style={{ transform: [{ scaleY: -1 }]}}>
<ChatItem />
</View>)
Enjoy it)

As described in the other answers, currently the best way to go about doing this is to apply a transformation to the FlatList's containing view that inverts it.
return (
<View style={{ transform: [{ scaleY: -1 }] }} >
<FlatList
renderItem={this._renderItem}
...
/>
</View>
);
Then apply the same transformation to each list item to ensure that they are correctly reoriented.
_renderItem = (info) => {
return (
<View style={{ transform: [{ scaleY: -1 }] }}>
{this.props.renderItem(info)}
</View>
);
}
I've written a package that does exactly that, all you need to do is use this in place of a standard FlatList component and pass the inverted property.
https://www.npmjs.com/package/react-native-invertible-flat-list

Related

React Native Expo Flatlist pull to refresh gesture not triggering on iOS but works fine on Android

I have a flatlist component rendered inside a flex: 1 view, that doesn't perform pull to refresh on iOS. The gesture itself doesn't work, as the list refuses to get pushed down, but works fine on Android.
Here is my flatlist code, and the only code in the screen.
<FlatList<any>
style={{
flex: 1,
// marginTop: 10,
}}
contentContainerStyle={{ flexGrow: 1 }}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
data={ordersDetails?.docs}
keyExtractor={(item) => item._id}
renderItem={renderItem}
bounces={false}
refreshControl={
<RefreshControl
refreshing={loading}
onRefresh={() => {
fetchOrders(getOrdersListRefreshing);
}}
/>
}
initialNumToRender={10}
onEndReachedThreshold={0.01}
onEndReached={() => {
fetchOrders(getOrdersListNoLoading);
}}
removeClippedSubviews
maxToRenderPerBatch={5}
updateCellsBatchingPeriod={200}/>;
renderItem is nothing but a text component.
Sorry I am a bit new to React Native.
Thanks in advance!
you can remove style props and containerStyle props and bounce
There is no need for a separate flex value of flex style. And if you put bounce to false ios, you can't refresh it.

Flatlist React native card's onPress does not trigger on Android

I am using Expo for my app. I have a horizontal Flatlist where I render my data react native paper's Card. I saw Card have onPress function. I used that to navigate the another page. But onPress function does not trigger on Android device. I know React native's touchable-opacity have positioning issue on Android. I tried hitSlop and inline styling zIndex but still does not work. I also wrap my card with react-native's touchable-opacity and play with positioning still did not help me, only it works when i used react-native-gesture-handler's touchable-opacity but then it does not work on IOS. Hope anyone can help me...
import React from 'react';
import { Card } from 'react-native-paper';
import { useNavigation } from '#react-navigation/native';
interface Iprops {
item: string;
}
export default function RenderCard({ item }: Iprops) {
const navigation = useNavigation();
return (
<Card
hitSlop={{ "bottom": 30, "top": 30, "right": 30, "left": 30 }}
onPress={() => {
navigation.navigate(`detail`, { // THIS DOES NOT TRIGGER ON ANDROID
"id": `${item.pk}`
});
}}
style={{ "marginBottom": 20 }}>
<Card.Cover
source={{ "uri": `${item.img_url}` }} />
<Card.Actions>
<Card.Title title={item.name} subtitle="Card Subtitle" />
</Card.Actions>
</Card>
);
}
I've noticed that using onPressIn or onPressOut does work on Android within an absolute positioned flatlist, but onPress does not work. I hope this might be of help to someone out there looking for an answer.
You'll need to use the TouchableOpacity element from react-native-gesture-handler.
Like you told, you should add touchable opacity to the element you are rendering on the card. TouchableOpacity can be tricky. So, first give it a styling of borderWidth:1 and borderColor to see the actual touchable area on the screen. Then you start to bring them together with the icon or the image or whatever you are rendering. TouchableOpacity works but the positioning can be tricky. You have to understand it to use it better. Think touchableOpacity as a view with borders then it'll be easier to grasp. Also, if you dont give touchableOpacity an absolute position in the styling it will be out of the screen somewhere, I was never be able to bring it to somewhere that I can see to position it. So you can add 'position' as well.
I gave up on positioning and render my component's based on Platform.
Platform.OS === `ios` ?
<Card
onPress={() => {
navigation.navigate(`detail`, {
"id": `${item.pk}`
});
}}
style={{ "marginBottom": 20 }}>
<Card.Cover source={{ "uri": `${item.img_url}` }} />
<Card.Actions>
<Card.Title title={item.name} subtitle="Card Subtitle" right={Beer} />
</Card.Actions>
</Card> :
<TouchableOpacity
onPress={() => {
navigation.navigate(`detail`, {
"id": `${item.pk}`
});
}}
>
<Card style={{ "marginBottom": 20 }}>
<Card.Cover source={{ "uri": `${item.img_url}` }} />
<Card.Actions>
<Card.Title title={item.name} subtitle="Card Subtitle" right={Beer} />
</Card.Actions>
</Card>
</TouchableOpacity>;

React Native Fixed Bottom Input Keyboard (Social Media Post UI)

I am trying to create a view post ui for a social media feature of our app. We have a post (the flatlist header component), the comments (the flatlist), and a fixed bottom text input that leverages zIndex to be placed over the flatlist to post comments. The problem is I cannot for the life of me figure out how to properly use a keyboard avoiding view to somehow push this fixed input up when the keyboard is shown. Is there any way to do this or maybe a simpler approach to this ui that I'm missing? I have tried using the keyboard height and adding it to the bottom positioning of the input but its slow and definitely isn't viable.
Code:
<View style={{flex: 1}}>
<View
style={{
...styles.container,
marginTop: 0,
zIndex: 1,
}}>
<FlatList
style={{minHeight: '100%'}}
data={new Array(15)}
renderItem={({item, index}) => <CommentRow index={index} />}
keyExtractor={(item, index) => `${index}`}
ListHeaderComponent={renderHeader}
contentContainerStyle={{paddingBottom: 70}}
/>
</View>
<View
style={{
position: 'absolute',
bottom: 0,
backgroundColor: 'white',
width: '100%',
zIndex: 2,
padding: 15,
borderTopWidth: 1,
borderColor: '#f2f2f2',
}}>
<View style={styles.inputContainer}>
<TextInput style={styles.input} placeholder="Add a comment.." />
<View style={styles.inputTextContainer}>
<Text style={styles.inputText}>Post</Text>
</View>
</View>
</View>
</View>
);
I'm not a fan of keyboard avoiding view because of some bad experiences I had. However, I'll give some tips on how to achieve the same effect with React Native core Animated Library.
Since your view is elevating (Style it as an absolute view) and when the keyboard appears, you can get the height of the keyboard with Keyboard addListener's callback.
componentDidMount(){
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
}
_keyboardDidShow(e) {
const keyboardHeight = e.endCoordinates.height,
}
Once you get the keyboard height, you can animate the absolute view using translateY to the position that aligns with the keyboard without any gaps. This will provide an nice animation similar to that you tried to accomplish with keyboard avoiding view. I've done that in few apps as well and it works great.
For anyone looking to do anything like this - what I ended up doing is using an useEffect hook to monitor when "keyboardWillShow" and then created a function to animate the view above the keyboard once its about to show. A lot of the answers I had seen used "keyboardDidShow" but it makes the UI slow and really ugly. Here's the code:
useEffect(() => {
Keyboard.addListener('keyboardWillShow', _keyboardWillShow);
Keyboard.addListener('keyboardWillHide', _keyboardWillHide);
// cleanup function
return () => {
Keyboard.removeListener('keyboardWillShow', _keyboardWillShow);
Keyboard.removeListener('keyboardWillHide', _keyboardWillHide);
};
}, []);
const _keyboardWillShow = (e) => {
const keyboardHeight = e.startCoordinates.height;
Animated.timing(topValue, {
toValue: -keyboardHeight,
duration: 50,
useNativeDriver: true,
}).start();
};
const _keyboardWillHide = (e) => {
Animated.timing(topValue, {
toValue: 0,
duration: 0,
useNativeDriver: true,
}).start();
};
Where topValue is a stateful Animated value. All you need to do after this is add the topValue variable to your animated view using transform and you're done!

Image flickers on first load in ReactNative

I am trying to build a ReactNative Application with an animated button. The problem is that this animation does not work correctly the first time after the App is started. There is some white flickering. But after the animation ran wrong the first time everything works as expected:
I have already tried to preload the image in several ways, but without any success.
This is my minimal working example, note that if there are several different images the flickering occurs if a new image is loaded (e.g. I have two blue buttons and after I tapped the first one, the second one will work fine, but if I then tap an orange button it once again flickers for the first time, at least if I have not tapped another orange button after app start.):
import React, { Component } from 'react';
import {StyleSheet, Text, TouchableWithoutFeedback, View, Image, ScrollView,
Button, BackHandler} from 'react-native';
export default class Touchables extends Component {
constructor(props) {
super(props);
this.state = {alarm1: (<Image source={require("./assets/alarmoff.png")}
style={styles.imageButton}/>),
}
}
componentWillMount(){
//trying to preload all Images, but it does not help.
(<Image source={require("./assets/alarmon.png")} style=
{styles.imageButton}/>)
}
render() {
return (
<ScrollView style={styles.contentContainer}>
<View style={{flex: 3, flexDirection: 'row'}}>
<View style={styles.container}>
<TouchableWithoutFeedback onPressIn={() => this.setState({alarm1:
<Image source={require("./assets/alarmon.png")} style={styles.imageButton}/>})} onPressOut={() => this.setState({alarm1: <Image source={require("./assets/alarmoff.png")} style={styles.imageButton}/>})}>
<View style={styles.button}>
{this.state.alarm1}
</View>
</TouchableWithoutFeedback>
<Text style={styles.text}>This button flickers on first click. Restart App completly to see the issue. Reloading is not enough.</Text>
</View>
</View>
<View>
<Button
onPress={() => BackHandler.exitApp()}
title="Exit App"
color="#841584"
accessibilityLabel="Android only I guess."
/>
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 2,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 30
},
button: {
backgroundColor: '#fff',
borderRadius: 20,
padding: 10,
marginBottom: 20,
shadowColor: '#303838',
shadowOffset: { width: 0, height: 5 },
shadowRadius: 10,
shadowOpacity: 0
},
contentContainer: {
paddingVertical: 20,
flex: 1,
backgroundColor: '#fff',
},
text:{
color: '#000',
marginBottom: 30
},
imageButton: {
flex: 1,
width: 240,
height: 200,
marginBottom: -15,
marginTop: 10,
resizeMode: 'cover'
}
});
So my question is how can I stop the image from flickering after app start?
The full version of the little demo app I have build to show my problem is available on my Github Repository
There may be a performance issue while loading different resolution images. You can use https://github.com/DylanVann/react-native-fast-image module to load images.
you can add and link it as below
# Install
yarn add react-native-fast-image
# Automatic linking. (other linking methods listed below)
react-native link react-native-fast-image
after that u can import it and use it using like below example
import FastImage from 'react-native-fast-image'
const YourImage = () =>
<FastImage
style={styles.image}
source={{
uri: 'https://unsplash.it/400/400?image=1',
headers:{ Authorization: 'someAuthToken' },
priority: FastImage.priority.normal,
}}
resizeMode={FastImage.resizeMode.contain}
/>
I copied this example from that repo. you can find documentation also there. Try it. It will increase image loading performance. Then most probably flickering issue will be resolved.
For me, it was causing flickering issues when I put the Image component in a FlatList ListHeaderComponent component. So,
Code causing flickering:
ListHeaderComponent={HeadComponent}
The HeadComponent was basically inside render and had the code const HeadComponent = () => { return (<Image...
Code that fixed flickering:
ListHeaderComponent={this.renderHeader}
The renderHeader is a function that returned the same thing as HeadComponent using code renderHeader () { return (<Image...
Hope this helps someone.
It was causing flickering issues when I put the Image component in a FlatList ListHeaderComponent component
In order to solve the issue I added useCallBack hook
const ListComponent = useCallBack(() => {
// your code
}, [])
ListHeaderComponent={ListComponent}
For me it solved the flickering issue
Well I have a workaround (sort of..).
In my componentDidMount() I do now set the button to its pressed state, wait for some time until the image is displayed and scaled, and then I set the state to off again, like so:
componentDidMount(){
this.setState({alarm1: <Image source={require("./assets/alarmon.png")} style={styles.imageButton}/>})
setTimeout(()=>{this.setState({alarm1: <Image source={require("./assets/alarmoff.png")} style={styles.imageButton}/>})}, 1000);
}
I tried to lower the timeout to less than a second, but then on my old (and slow) phone the flickering started again on first press after app load.
This obviously leads to the button state beeing changed after the app loaded, but if all buttons flicker once after app start, that is better than every button flickering on first press in my opinion.
I would however be glad if anybody could tell me the real way, how to resolve this.
For anyone who still have this problem : this is another way to fix
<Image
source={{ uri: your_path }}
defaultSource={{ uri: your_path }}
resizeMode="cover"
style={{width: 100,height: 100}} />
A stupid way:
<ImageBackground
defaultSource={require('./ui/pay0.png')}
source={require('./ui/pay0.png')}
style={{flex: 1, position: 'relative', zIndex: 9999, elevation: 5}}
/>
{/* Cache the remaining pictures to prevent flickering */}
<ImageBackground
source={require('./ui/pay1.png')}
style={{position: 'absolute', width: 1, height: 1}}
/>
If you are using expo, you can use Asset.loadAsync. See: https://docs.expo.io/versions/latest/sdk/asset.
In App.js, I like to wait for all static assets to be loaded before showing any screen.

ListView's onEndReached, in react native, is not working as expected

This is my code for a component's render.
render() {
let loadMore;
let hasMore = this.props.end && this.props.photos.length < this.props.end;
if (hasMore) {
loadMore = (
<View style={globalStyles.ContChild}>
<Button
onPress={() => this.loadMoreItems()}
containerStyle={globalStyles.bottomButton}
style={globalStyles.buttonText}
>
Load more
</Button>
</View>
);
}
return (
<View>
<ListView
dataSource={this.ds.cloneWithRows(this.props.photos)}
renderRow={this._renderPhoto}
contentContainerStyle={{
flexDirection: 'row',
flexWrap: 'wrap',
marginLeft: 4,
}}
enableEmptySections={true}
initialListSize={12}
pageSize={12 * 3}
onEndReached={hasMore ? (() => this.loadMoreItems()) : null}
onEndReachedThreshold="32"
/>
{loadMore}
</View>
);
}
It is intended that the ListView, when scrolled to the bottom but 32px or less, perform an additional data load.
Right now the data load works via a button labeled "Load More", as it is generated in the if-condition in the render method. The button is conditionally added inside the View component, right below the ListView.
Currently, when you click the button, the loadMore() logic is executed and loads more pictures accordingly. The loaded data is a list of pictures (the listview is a photo gallery).
I wanted to remove the button and use the when the listview is scrolled to the bottom logic, by using the onEndReached handler. Right now both logics coexist in my system, but there is a problem -or misunderstanding since I started yesterday on this project- with the onEndReached logic, even if using the threshold:
Right now the onEndReached logic is executed eagerly, on each render cycle (IIRC a new render cycle is triggered when the data changes, and the data changes on each loadMorecall), as if each render cycle considered that onEndReached should trigger. However: the listview is not scrolled to the end in any case. It is still in the very scrolling top.
My purpose: I don't want the whole pictures be loaded eagerly, but in a scroll-paginated fashion, i.e. when the scrolling bottom is reached, load more elements automatically (with no need to either press the Load More button -since I will remove that button- and no eager loading but scrolling).
My question: What am I doing wrong regarding this event and how could I fix it?
You can try this one:
<ListView
ref={(listView) => { _listView = listView; }}
dataSource={this.ds.cloneWithRows(this.props.photos)}
renderRow={this._renderPhoto}
contentContainerStyle={{
flexDirection: 'row',
flexWrap: 'wrap',
marginLeft: 4,
}}
enableEmptySections={true}
initialListSize={12}
pageSize={12 * 3}
onEndReached={hasMore ? (() => this.loadMoreItems()) : null}
onEndReachedThreshold="32"
/>
I missed to use ref in my ListView, and was facing the same problem. Once I added those, my pagination is working correctly.

Categories

Resources