So i am building an app where there will be a page just for a gif animation. I want so that after that gif animation is done, it will automatically go to the next page. How do i do that?
Am i supposed to use a timer, so that the gif page will only appear for 3 seconds and then it goes to the next page? Or what is the best way to approach this?
how i manage routing:
render() {
return (
<Navigator
renderScene={this.renderScene.bind(this)}
navigator={this.props.navigator}
/>
);
}
renderScene(route, navigator) {
return (
...);
}
Well it's not clear from your question what navigation scheme you're using, but regardless, the process is trivially done if the length of the GIF is known. If it's unknown, I suspect you're in for a lot more work, but otherwise simply:
const GIF_LENGTH = 5 * 1000; // 5 second gif
class MyComponent extends React.Component {
componentDidMount() {
setTimeout(() => nav.push({ name: 'NextPage' }), GIF_LENGTH);
}
render() {
return < gif display here >;
}
}
Obviously replace nav.push(...) with the particulars of however you manage routing in your application.
Related
I am building an app that has three tab navigation.
Each tab wraps a WebView and displays the uri webview is directing.
So, I have three WebViews running in an app.
/* each screen wraps a webview */
/screens
-HomeScreen
-ChatScreen
-ProfileScreen
In each screen, I have back button mapped to call webviewRef.current.goBack() just like below.
useEffect(() => {
const handleBackButtonPress = () => {
if (webviewRef.current) {
webviewRef.current.goBack();
return true;
}
return false;
};
BackHandler.addEventListener("hardwareBackPress", handleBackButtonPress);
return () =>
BackHandler.removeEventListener(
"hardwareBackPress",
handleBackButtonPress
);
}, []);
return (
<Screen>
<WebView
source={{
uri: "http://0.0.0.0:3000/",
}}
ref={webviewRef}
onMessage={handleMessage}
injectedJavaScript={script}
/>
</Screen>
);
It works seamlessly in each webviews, but the problem happens when I move on to different screen(or webview). To help you grasp, I will list the steps to generate the problem I faced.
I open the app and view HomeScreen webview.
I look around in HomeScreen webview (I can use back button)
I decide to move to ProfileScreen and touch profile icon in the tab navigation
I am on ProfileScreen webview.
I look around in ProfileScreen webview (I visit links)
I touch hardware back button and it does not work. (to elaborate, it does nothing.)
My guess is:
the cleanup function in useEffect is somehow removing 'hardwareBackPress' event..
advice appreciated.
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 hoping this is a known problem as I can't provide much to go on to get help solving it.
I'm using react-navigation for my app with the following setup:
AppContainer
Splash Screen
Setup Screen (A stack navigator)
Main screen (A stack navigator)
When my app starts it goes to the splash screen which decides if this is the first time running or not and depending on this decision calls this.props.navigation.navigate() with either main screen or setup screen.
So far this all works and is fairly standard.
When my app is launched for the first time the user is sent to the setup screen where they navigate through a series of screens entering data and selecting a next button to proceed to the next screen. This is where the problem occur. My first screen simply has some text and a next button (which is a regular button component) which when clicked calls this.props.navigation.push('nextviewname', {data: data}) to move to the next view.
The next view contains a textinput as well as back and next buttons which is where I'm having problems. When I reach this screen after freshly installing a release version of my app onto my Android X phone none of the inputs work. I can't:
Click next of back
Click the back arrow in the top left that is part of the header
Click the text input (the cursor does briefly show up in the text input but the keyboard never appears)
Click the hardware back key
On very rare occasions some of the above does work (e.g. the text input will sometimes work) and sometimes I'll even make it to the next step of my setup but it's rare that I make it all the way through
Weirdly this all works when I'm debugging my app on my phone.
Update: I've just tested on an Android 9 emulator and I'm getting the same issue.
Update 2: This is getting weird, when the app is in a broken state I can still bring up the react native debug menu however when I click Toggle Inspector nothing happens (i.e. I don't get the inspector UI). It's looking like this is somehow breaking everything.
Has anyone seen/solved this issue before? At the moment it's effectively made my app useless.
Update 3: Some code to hopefully make things clearer:
const SetupUser = createStackNavigator(
{
SetupUser: WelcomeScreen,
SetupName: UserName,
SetupCurrentWeight: CurrentWeight,
SetupGoalWeight: GoalWeight,
SetupGoalDate: GoalDate,
Summary: Summary,
LogWeight: LogWeight,
},
{
defaultNavigationOptions: {
headerStyle: {
backgroundColor: '#001830',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
},
},
);
const MainApp = createStackNavigator(
{
LogWeight: LogWeight,
LogWeightSummary: LogWeightSummary,
},
{
defaultNavigationOptions: {
headerStyle: {
backgroundColor: '#001830',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
},
},
);
export default createAppContainer(
createSwitchNavigator(
{
MainApp: MainApp,
SplashScreen: SplashScreen,
SetupUser: SetupUser,
},
{
initialRouteName: 'SplashScreen',
},
),
);
In getting this code snippit together (I've removed the tab navigator as the error is still there even without it) I think I've managed to track down the source of the issue however I'm still not sure how to fix it. The first view loaded is the splash screen which looks like this:
export default class SplashScreen extends React.Component {
constructor(props) {
super(props);
GoBinder.verifyDatabase()
.then(GoBinder.getLastUser())
.then(user => {
this.props.navigation.navigate(
user.user == null ? 'SetupUser' : 'MainApp',
);
})
.catch(error => {
GoBinder.toast('Error while checking initial user state: ' + error);
});
}
render() {
return (
<View style={styles.container}>
<ActivityIndicator />
<StatusBar barStyle="default" />
</View>
);
}
}
In the above code, GoBinder.verifyDatabase() and GoBinder.getLastUser() are calls to native code which perform some database operation to get things setup and check to see if there are any existing users. I believe the issue is this.props.navigation.navigate is firing too quickly which is causing react-navigation to load the next screen but get messed up in the process.
I've tried following the suggestions in this post https://www.novemberfive.co/blog/react-performance-navigation-animations about moving blocking code into InteractionManager.runAfterInteractions under componentDidMount however this made no difference. However, it I manually trigger the move to the new screen using a button everything works correctly so its really looking like the act of programatically changing screens is messing things up.
Update 4:
Looks like I jumped the gun a bit, its still freezing up a fair bit, moving my answer into an update:
So I'm not sure if this is the best solution but I have found a workaround. I am now calling my database code in InteractionManager.runArfetInteraction() as suggested in https://www.novemberfive.co/blog/react-performance-navigation-animations. I am then calling setState with the result of the DB functions to store the result. Finally, I am using a callback on setState to trigger the actual navigation. My complete working code is:
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
GoBinder.verifyDatabase()
.then(
GoBinder.getLastUser().then(user => {
this.setState(
{
user: user.user,
},
() => {
this.props.navigation.navigate(
this.state.user == null ? 'SetupUser' : 'MainApp',
);
},
);
}),
)
.catch(error => {
GoBinder.toast('Error while checking initial user state: ' + error);
});
});
}
Thanks for your help
I have created an app and added splach screen. It takes 1 second to load the app on Android Emulator. However after I publish the app in the store, it takes 4 seconds to load.
This is pretty annoying for such a simple app.
I was thinking it was because of _loadResourcesAsync func to load pictures. Therefore I commented out those lines but nothing has changed.
Any recommendation to speed up my app launch.
Here you can find my app.js
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { AppLoading, Asset } from 'expo';
import AppNavigator from './navigation/AppNavigator';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoadingComplete: false,
};
}
render() {
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
return (
<AppLoading
startAsync={this._loadResourcesAsync}
onError={this._handleLoadingError}
onFinish={this._handleFinishLoading}
/>
);
} else {
return (
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<AppNavigator />
</View>
);
}
}
_loadResourcesAsync = async () => {
return Promise.all([
Asset.loadAsync([
// require('./assets/images/big_bottle.png'),
// require('./assets/images/bottle.png'),
// require('./assets/images/coffee.png'),
// require('./assets/images/juice.png'),
// require('./assets/images/menu.png'),
// require('./assets/images/tea.png'),
// require('./assets/images/water-glass.png'),
]),
]);
};
_handleLoadingError = error => {
// In this case, you might want to report the error to your error
// reporting service, for example Sentry
console.warn(error);
};
_handleFinishLoading = () => {
this.setState({ isLoadingComplete: true });
};
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});
Based on my experience, hybrid apps, in general, tend to launch slower.
Based on my tests, even a completely blank Expo app, based on the device performance, can take anywhere from 1-4s to launch. This is the time it takes from the moment the user taps (opens) the app to the time the splash screen gets hidden (and the first app screen is visible).
What you can do to speed it up is a really broad topic. Here are two recommendations, that helped a lot for a project of mine:
Cashe your assets.
Bundling assets into your binary will provide for the best user experience as your assets will be available immediately. Instead of having to make a network request to the CDN to fetch your published assets, your app will fetch them from the local disk resulting in a faster, more efficient loading experience.
Always load a cached version of your app first.
You can configure the "updates" section in your app.json to always starts with the cached version of your app first (fallbackToCacheTimeout: 0) and continue trying to fetch the update in the background (at which point it will be saved into the cache for the next app load).
Unfortunately, as of today, it looks like we're kind of limited to what we can do to improve further the initial loading time on a blank app. There isn't really a reliable way to know how long the 'check for updates' process takes, the general React Native initialization time, the 'load all the JS' time.
There is a feature request for tools for improving the startup time, would be great if the Expo team introduces something similar anytime in the future.
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