I have at the moment a component SplashScreen which I'm rendering first till my state is set. I would like somehow to find a way how to still show this component while my webview is loaded. I added the onLoadEnd to my webview and looks like I get my message back when its finished loading, the problem is that if I load first the splashscreen and wait for the state to be changed onLoadEnd actually will never be changed because the webview is not yet rendered. Is there a good method how to do this?
My solution was actually quite simple, the WebView component can have the param renderLoading which for me was not working, I figured out it was because also startInLoadingState needed to be defined.
So my WebView looks somehow like this:
<WebView
ref={MY_REF}
source={source}
renderLoading={this.renderLoading}
startInLoadingState
/>
This would be my approach:
constructor(props){
super(props);
this.state = { webviewLoaded: false };
}
_onLoadEnd() {
this.setState({ webviewLoaded: true });
}
render() {
return(
<View>
{(this.state.webviewLoaded) ? null : <SplashScreen />}
<WebView onLoadEnd={this._onLoadEnd.bind(this)} />
</View>
)
}
This way, the webview is rendered but it is placed behind the SplashScreen. So the webview starts loading while the SplashScreen is being displayed.
I had a similar problem and I managed to solve it temporarily with this:
loadEnd () {
this.setState({ webViewLoaded: true }):
}
render () {
const { webViewLoaded } = this.state;
return (<View>
{!webViewLoaded && <LoadingComponent /> } -- or spinner, whatever
<WebView
style={(webViewLoaded) ? styles.webView : styles.loading}
onLoadEnd={() => this.loadEnd.bind(this)} />
</View);
}
const styles = StyleSheet.create({
webView: { -- your styles ---},
loading: {
width: 0,
heigt: 0
}
});
not sure if exactly this helps you but you can try similar approach. I will probably change this to something more convenient. Not sure if there are possibilities to animate these changes because Im still pretty newbie in React Native.
edit: added hiding the spinner/loading element
Related
I have an App with two Views, blue view must be shown only when app is in active state (foreground) and the green view must be shown when app is put on background (e.g. clocking on hardware square button on Android).
The following picture shows what I currently get when I put the App in background state:
The following one is what I want to do when App is put on background (on iOS works, on Android not so well):
And this is the implementation.
Please, ignore for the moment that the component for iOS is almost identical to that for Android except for the style of the View. I was doing other tests with different components and for the moment it's okay for me to keep them like this. The difference between the two components (iOS / Android) lies in the style, because in iOS the zIndex works, while in Android I have to use elevation to overlap the views.
const showSecurityScreenFromAppState = appState =>
['background', 'inactive'].includes(appState);
const withSecurityScreenIOS = Wrapped => {
return class WithSecurityScreen extends React.Component {
constructor(props) {
super(props);
}
state = {
showSecurityScreen: showSecurityScreenFromAppState(AppState.currentState),
};
componentDidMount() {
AppState.addEventListener('change', this.onChangeAppState);
}
componentWillUnmount() {
AppState.removeEventListener('change', this.onChangeAppState);
}
onChangeAppState = nextAppState => {
const showSecurityScreen = showSecurityScreenFromAppState(nextAppState);
this.setState({showSecurityScreen});
};
//this.state.showSecurityScreen
render() {
return (
<>
<View style={[styles.container, {zIndex: this.state.showSecurityScreen ? 1 : -1}]}>
<View style={{flex: 1, backgroundColor: globalStyles.colors.customerGreen}}>
<View style={styles.upperView}>
<Text style={styles.upperViewText}>MyApp</Text>
</View>
</View>
</View>
<Wrapped {...this.props}/>
</>
);
}
};
};
const withSecurityScreenAndroid = Wrapped => {
return class WithSecurityScreen extends React.Component {
constructor(props) {
super(props);
}
state = {
showSecurityScreen: showSecurityScreenFromAppState(AppState.currentState),
};
componentDidMount() {
AppState.addEventListener('change', this.onChangeAppState);
}
componentWillUnmount() {
AppState.removeEventListener('change', this.onChangeAppState);
}
onChangeAppState = nextAppState => {
const showSecurityScreen = showSecurityScreenFromAppState(nextAppState);
this.setState({showSecurityScreen});
};
//this.state.showSecurityScreen
render() {
return (
<>
<View style={[styles.container, {elevation: this.state.showSecurityScreen ? 1 : -1}]}>
<View style={{flex: 1, backgroundColor: globalStyles.colors.customerGreen}}>
<View style={styles.upperView}>
<Text style={styles.upperViewText}>MyApp</Text>
</View>
</View>
</View>
<Wrapped {...this.props}/>
</>
);
}
};
};
export const withSecurityScreen =
Platform.OS === 'ios' ? withSecurityScreenIOS : withSecurityScreenAndroid;
There is not problems in the code, it works very well in iOS, but on Android sometimes it works and sometimes it doesn't. I suspect the cause is that Android OS, when the square button is pressed, it takes a screenshot of the current view and uses it as a background image of the App. Sometimes, however, it happens that the change of layer (elevation) is faster than the Android screenshot and therefore I can see the green screen in the background, as Android is using it as an image.
Is there a way to synchronize these operations? That is: can I make sure that when I press the square button, Android waits for the view to change and then let it go in the background?
Note: I have tested the functioning of elevation when the app is in the foreground, simply inverting the values of elevation when it is in the background or in the foreground, I assure that elevation is working.
Note: in the following animation, when the View in background tasks is white, is a fail. When the View becomes green in background, is a success. Why is random?
Update 1
I realize now that the question may be badly set.
First of all I used incorrect terms to refer to the background status of the apps, the view I speak of in Android is called Recent Apps (or Task View). Then, secondly, in Recent Apps (Task View) what Android does, after pressing the square or circle button, a screenshot of the current view and then shows the Tasks View, using as the image for my App, the screenshot that he just did. The problem is quite annoying, because (as shown in the GIF above) sometimes the screenshot is done AFTER the change of view. Sometimes instead (and often times) the screenshot occurs BEFORE the change of view (the green one).
This is why sometimes in Task View I see the blue screen and sometimes the green one. It's almost a competition problem, let's say. My approach is probably not suitable, I should find another one. Is it possible to manually change the image used in Task View for my App?
You can try like this:
onChangeAppState = async nextAppState => {
const showSecurityScreen = await showSecurityScreenFromAppState(nextAppState);
this.setState({showSecurityScreen});
};
I'm using Flatlist in HomeScreen and it shows me multiple posts. So now what I want is whenever I Signout from the app or close the app and then open the app, I should be able to see the first item from Flatlist.
I've tried using scrollToIndex inside my render function but it gave me an error - undefined is not an object (evaluating '_this2.refs.flatListRef.scrollToIndex')
<FlatList
data={this.state.data}
ref={(ref) => { this.flatListRef = ref; }}
renderItem={ ({item,index}) => this._renderItem(item,index) }
extraData={[ this.state.data, this.state.checked ]}
/>
And this is what I tried using in componentDidMount and inside render function this.refs.flatListRef.scrollToIndex({animated: true,index:0}); but didn't work.
ComponentDidMount callbacks won't run after the user leaves the app and resumes it later. You have to use AppState instead:
AppState can tell you if the app is in the foreground or background, and notify you when the state changes. [Source]
Adapt the given example to your needs and scroll with this.flatListRef.scrollToIndex({animated: true,index:0})}
import {
ScrollView,
} from 'react-native';
import {useScrollToTop} from '#react-navigation/native';
const ref = React.useRef(null);
useScrollToTop(ref);
const goToTopClickAction = () => {
ref.current?.scrollTo({
y: 0,
animated: true,
});
};
add ref={ref} in ScrollView tag
< ScrollView ref={ref}> < /ScrollView >
I have my code setup as such:
export default class HomeScreen extends Component {
constructor() {
super();
}
componentDidMount () {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
}
_keyboardDidShow = () => {
console.log('keyboard did show')
}
_keyboardDidHide = () => {
console.log('keyboard did hide')
}
render() {
return (
<Container styles={styles.container} >
<Content styles={styles.content} contentContainerStyle={marginLeft=this.state.marginLeft}>
<Image
style={styles.bgImg}
source={Images.bgImg}
>
</Image>
<Image
style={styles.logo}
source={Images.logo}
>
</Image>
<Text style={styles.slogan}>This is the title</Text>
<Form style={styles.search_form}>
<Item rounded floatingLabel style={styles.search}>
<Label style={styles.search_label}>Where are you headed?</Label>
<Input style={styles.search_input} />
<Button full rounded style={styles.search_btn}>
<Icon name="search"></Icon>
</Button>
</Item>
</Form>
</Content>
</Container>
);
}
}
I want basically the Content component of native-base to avoid the keyboard. I have my logo at the top, the slogan below it and the form at the bottom of the screen by giving some absolute positioning. At this point, the content component moves way up the screen which I don't want. What I want is the logo and the slogan staying right at the top of the screen but the form which is at the bottom of the page; to move up.
Here's what I've researched so far:
Found out that there is actually a component from react native called KeyboardAvoidingView and I played around with it but keeping the Logo, background image and the rest of the content inside the KeyboardAvoidingView made all the content not show in the screen.
Later I found out that the native base component 'Content' itself extends KeyboardAvoidingView, so there was no need to use it in the first place. But I don't think KeyboardAvoidingView is working with my versions of react native and native base.
So at last, I decided that this was a bug and I would use the Keyboard module of the react native instead to do some custom work, which is where I'm at right now, code-wise.
The Question
The console logging inside _keyboardDidShow and _keyboardDidHide are working, which means now I just need to know how to change the style of a component on keyboardDidShow and keyboardDidHide. Any help is appreciated, of course!
I'm really new to react native so any suggestions to better improve my workflow will be taken seriously.
I have already stumbled across the same problem! I'd recommend you save the keyboard width on state, with, for example:
keyboardDidShow = e => this.setState(p => ({ ...p, height: e.endCoordinates.height })
keyboardDidHide = () => this.setState(p => ({ ...p, height: 0 })
then, having this height, you can make your UI depend on that value. After you make sure that is working, in order for it not to jump between positions, use Animated to have a seamless transition between the positions. Hope this helps!
I'm having a serious problem with React Native on Android.
Let's say I have the following render method:
render() {
let stuff = this.state.showStuff ? <Text style={styles.stuff}>Stuff</Text> : null
return (
<View>
{ stuff }
</View>
)
}
Really simple. If I want to hide the "Stuff", I just call:
this.setState({ showStuff: false })
This works fine on iOS, but on Android, if styles.stuff define a backgroundColor, the component will be re-rendered without the "stuff" content, but with it's background!
Now I have no idea on how to remove elements from my component, since this weird behavior broke how I used to think react native works.
I found out that react-native-searchbar was causing the issue on non-related views. Removing that component solved this and other non-related issues on my project.
I am using React Native's <WebView> component.
The documentation has no mention of how to handle the keyboard hiding the webview when the webview's HTML contains an <input> which becomes focused on in Android.
Has anyone managed to solve this?
I have found a library that seems to work for regular <View>, but not a <WebView>.
you can wrap your WebView with a KeyboardAvoidingView
<KeyboardAvoidingView
behavior={Platform.select({ ios: "position", android: null })}
enabled
contentContainerStyle={{ flex: 1 }}
keyboardVerticalOffset={Platform.select({ ios: offset, android: 20 })}
style={{ flexGrow: 1 }}
>
<WebView/>
</KeyboardAvoidingView>
Have you thought about responding to system level events for the keyboard appearing and disappearing, then adjusting the WebView size appropriately?
There is an old question about how to handle this, it may or may not still be relevant. This answer in particular shows how to handle keyboard events. https://stackoverflow.com/a/33585501/1403
DeviceEventEmitter.addListener('keyboardDidShow',(frames)=>{
if (!frames.endCoordinates) return;
this.setState({keyboardSpace: frames.endCoordinates.height});
});
DeviceEventEmitter.addListener('keyboardWillHide',(frames)=>{
this.setState({keyboardSpace:0});
});
You could use the frames.endCoordinates.height value to alter the height of your WebView, ensuring that the content is not hidden behind the keyboard.
Just raising awareness that this can also be simply achieved by using react-native-keyboard-spacer if you are not in for intricate keyboard control.
import KeyboardSpacer from 'react-native-keyboard-spacer';
class DemoApp extends Component {
render() {
return (
<View>
<WebView ... />
<KeyboardSpacer/>
</View>
);
}
}
I used WebView.onTouchStart and ScrollView.scrollTo.
This is work like charm for me.
import { useRef } from "react";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
const scrollRef = useRef();
const scrolldown = () => {
scrollRef.current.scrollTo((wp("100%") * 2) / 3);
};
<ScrollView ref={scrollRef} >
...
<WebView onTouchStart={scrolldown} >
...