How to gracefully close React Native Application for Android - android

I am new to React Native Application. I am using below code to close the application on clicking of a button.
BackHandler.exitApp();
return false;
But application is not properly ended and still is in Taskbar. So when I try to open the application again, componentWillMount never executes.
I am using below version of React Native.
react-native-cli: 2.0.1
react-native: 0.55.4
Any help or advice on how to cleanly close React Native Application?
Thanks in advance.

Google and Apple won't advice to force quit an application so you should avoid doing that so for better user experience.
If you need to recall a function when your app comes back from background you can use react-native's Appstate API. With Appstate you can listen for app states (active, background and inactive) and run your desired function.
AppState can tell you if the app is in the foreground or background,
and notify you when the state changes.
Below is a sample for requesting data and refreshing the list on every time screen comes back foreground.
Sample
import React, { Component } from 'react';
import { Text, View, FlatList, StyleSheet, AppState } from 'react-native';
export default class App extends Component {
state = {
data: []
}
requestItems = () => {
fetch('someurl').then((response) => response.json()).then((responseJSON) => this.setState({data: responseJSON.data}))
}
componentDidMount() {
this.requestItems()
AppState.addEventListener('change', this.requestItems);
}
componentWillUnmount() {
AppState.removeEventListener('change', this.requestItems);
}
renderItem = ({item}) => <Text>{item.text}</Text>
render() {
if (this.state.data.lenght === 0) return <Text>{'Loading'}</Text>
return (
<View style={styles.container}>
<FlatList data={this.state.data} renderItem={this.renderItem} keyExtractor={(item) => item.id} />
</View>
);
}
}

Related

React Native - navigation.addListner is not an object

App.js:
import React, { Component } from 'react';
import {View,Text} from 'react-native';
import { createDrawerNavigator } from 'react-navigation-drawer';
import {createAppContainer} from 'react-navigation';
import Epics from './screens/tmp';
import Pager from './screens/Pager';
const DrawerNavigator = createDrawerNavigator({
Home: {screen: Epics},
Page: {screen: Pager}
},{initialRouteName: 'Home'});
const Stack = createAppContainer(DrawerNavigator);
export default class App extends Component {
render() {
return <Stack />;
}
}
Trying to invoke fetch API everytime a user lands on screen using navigation addListner event
componentDidMount() {
this.loadFeed(); // fetch API
const { navigation } = this.props;
this.focus = navigation.addListener('willFocus', () => {
this.loadFeed(); // fetch API
});
}
React Navigation version from package.json:
"react-navigation": "^4.2.2"
"react-navigation-drawer": "^2.4.2"
Is there a better way to detect an active screen on app and invoke the fetch API? Or fix for the given error below?
Type Error: undefined is not an object (evaluating 'navigation.addListner'):
Error when the app launches
Update
I am trying to replicate following example in snack: https://snack.expo.io/HkrP8YPIf
What am I doing wrong? (I am new to React Native, please help me understand)
Try using the NavigationEvents component shipped with react-navigation 4.x. Just nest it anywhere inside your screen's render method.
Example:
import React from 'react';
import { View } from 'react-native';
import { NavigationEvents } from 'react-navigation';
const MyScreen = () => (
<View>
<NavigationEvents
onWillFocus={() => console.log('Screen will be in focus')}
onDidFocus={() => console.log('Screen is in focus')}
onWillBlur={() => console.log('Screen will blur')}
onDidBlur={() => console.log('Screen blurred')}
/>
{/*
other code
*/}
</View>
);
export default MyScreen;
This just means that navigation doesn't exist. So it looks like the component is supposed to receive it via props and isn't. That's all I can tell from this code.
Check your props keys, agree with James. Have one suggestion here, since you're expecting a props, you should handle this common error whether key exist or not(if else)

How to load the same last component class in react native using react navigation?

My application scenario is like, let say you have three components:
class ComponentOne extends Component {
render() {
return (
<View>
<Text>Component One</Text>
<Button
title='Go To Component Two'
onPress={() => this.props.navigation.navigate('two')}/>
</View>
);
}
}
class ComponentTwo extends Component {
render() {
return (
<View>
<Text>Component Two</Text>
<Button
title='Go To Component Three'
onPress={() => this.props.navigation.navigate('three')}/>
</View>
);
}
}
class ComponentThree extends Component {
render() {
return (
<View>
<Text>Component Three</Text>
<Button
title='Go To Component One'
onPress={() => this.props.navigation.navigate('one')}/>
</View>
);
}
}
export default createStackNavigator({one: ComponentOne,two: ComponentTwo,three: ComponentThree});
Now when I load the app the ComponentOne will be loaded, inside the ComponentOne when I click on the button Go To Component Two it will navigate me to the ComponentTwo, inside ComponentTwo when I click on the button Go To Component Three it will navigate me to the ComponentThree and so on. Now let say I am in ComponentTwo and on the same time I close the application in the phone and I open the app switcher and clean all the running apps and load the same app again, so, it will be again loaded with ComponentOne.
My question is how to program the react navigation to continue from the same component where last time I left, even after cleaning the app from a background (cleaning all apps in app switcher)?
Is there any builtin way in react navigation to do this? Can anyone tell? Examples will be more appreciated. Thanks!
All Navigators have onNavigationStateChange where you can handle the navigation state changing. Example code:
import React from 'react';
import { AsyncStorage } from 'react-native';
import { createStackNavigator, NavigationActions } from 'react-navigation';
const Navigator = createStackNavigator({
ComponentOne: {
screen: ComponentOne,
},
ComponentTwo: {
screen: ComponentTwo,
},
ComponentThree: {
screen: ComponentThree,
},
}, {
initialRouteName: 'ComponentOne',
});
class App extends Component {
constructor(props) {
this.navigator = React.createRef();
}
componentDidMount() {
try {
// Retrieve the last route
const value = AsyncStorage.getItem('lastNavigationRoute').then((result) => {
if (result) {
this.navigator.current.dispatch(NavigationActions.navigate({
routeName: result,
}));
}
});
} catch (e) {
// handle the error
}
}
handleStateChange = (previousState, nextState) => {
// Here we get the Navigate action type only
const navigateAction = NavigationActions.navigate({ routeName: 'dummyRoute' });
if (action.type === navigateAction.type) {
try {
// Saving the last route
AsyncStorage.setItem('lastNavigationRoute', nextState.routeName);
} catch (e) {
// handle the error
}
}
}
render() {
// You could also set a state with loader to handle loading from AsyncStorage
return (
<Navigator onNavigationStateChange={this.handleStateChange} ref={this.navigator} />
);
}
}
How it works:
On every navigation state changing you also save the last routeName
from Navigate action
When component did mount, you check for saved
route in AsyncStorage
If there is a route, you dispatch the navigate action (it's possible to implement replace action as well)
Hope it helps.
i dont think that there is a solution directly using react-navigation.
What i think you could do is to save a value of the current screen to the storage of the phone and then use this value on app start to detect which screen to show

`componentDidMount()` function is not called after navigation

I am using stackNavigator for navigating between screens. I am calling two API's in componentDidMount() function in my second activity. When i load it first time, it gets loaded successfully. Then i press back button to go back to first activity. Then, if i am again going to second activity, the APIs are not called and I get render error. I am not able to find any solution for this. Any suggestions would be appreciated.
If anyone coming here in 2019, try this:
import {NavigationEvents} from 'react-navigation';
Add the component to your render:
<NavigationEvents onDidFocus={() => console.log('I am triggered')} />
Now, this onDidFocus event will be triggered every time when the page comes to focus despite coming from goBack() or navigate.
If the upvoted syntax that uses NavigationEvents component is not working, you can try with this:
// no need to import anything more
// define a separate function to get triggered on focus
onFocusFunction = () => {
// do some stuff on every screen focus
}
// add a focus listener onDidMount
async componentDidMount () {
this.focusListener = this.props.navigation.addListener('didFocus', () => {
this.onFocusFunction()
})
}
// and don't forget to remove the listener
componentWillUnmount () {
this.focusListener.remove()
}
The React-navigation documentation explicitly described this case:
Consider a stack navigator with screens A and B. After navigating to
A, its componentDidMount is called. When pushing B, its
componentDidMount is also called, but A remains mounted on the stack
and its componentWillUnmount is therefore not called.
When going back from B to A, componentWillUnmount of B is called, but
componentDidMount of A is not because A remained mounted the whole
time.
Now there is 3 solutions for that:
Subscribing to lifecycle events
...
React Navigation emits events to screen components that subscribe to
them. There are four different events that you can subscribe to:
willFocus, willBlur, didFocus and didBlur. Read more about them in the
API reference.
Many of your use cases may be covered with the withNavigationFocus
higher-order-component, the <NavigationEvents /> component, or the
useFocusState hook.
the withNavigationFocus
higher-order-component
the <NavigationEvents />
component
the useFocusState hook (deprecated)
withNavigationFocus
higher-order-component
import React from 'react';
import { Text } from 'react-native';
import { withNavigationFocus } from 'react-navigation';
class FocusStateLabel extends React.Component {
render() {
return <Text>{this.props.isFocused ? 'Focused' : 'Not focused'}</Text>;
}
}
// withNavigationFocus returns a component that wraps FocusStateLabel and passes
// in the navigation prop
export default withNavigationFocus(FocusStateLabel);
<NavigationEvents /> component
import React from 'react';
import { View } from 'react-native';
import { NavigationEvents } from 'react-navigation';
const MyScreen = () => (
<View>
<NavigationEvents
onWillFocus={payload => console.log('will focus', payload)}
onDidFocus={payload => console.log('did focus', payload)}
onWillBlur={payload => console.log('will blur', payload)}
onDidBlur={payload => console.log('did blur', payload)}
/>
{/*
Your view code
*/}
</View>
);
export default MyScreen;
useFocusState hook
First install library yarn add react-navigation-hooks
import { useNavigation, useNavigationParam, ... } from 'react-navigation-hooks'
function MyScreen() { const focusState = useFocusState(); return <Text>{focusState.isFocused ? 'Focused' : 'Not Focused'}</Text>; }
Here is my personal solution, using onDidFocus() and onWillFocus() to render only when data has been fetched from your API:
import React, { PureComponent } from "react";
import { View } from "react-native";
import { NavigationEvents } from "react-navigation";
class MyScreen extends PureComponent {
state = {
loading: true
};
componentDidMount() {
this._doApiCall();
}
_doApiCall = () => {
myApiCall().then(() => {
/* Do whatever you need */
}).finally(() => this.setState({loading: false}));
};
render() {
return (
<View>
<NavigationEvents
onDidFocus={this._doApiCall}
onWillFocus={() => this.setState({loading: true})}
/>
{!this.state.loading && /*
Your view code
*/}
</View>
);
}
}
export default MyScreen;
Solution for 2020 / React Navigation v5
Inside your screen's ComponentDidMount
componentDidMount() {
this.props.navigation.addListener('focus', () => {
console.log('Screen.js focused')
});
}
https://reactnavigation.org/docs/navigation-events/
Alternatively: Put the addListener method in constructor instead to prevent duplicated calls
React-navigation keeps the component mounted even if you navigate between screens. You can use the component to react to those events :
<NavigationEvents
onDidFocus={() => console.log('hello world')}
/>
More info about this component here.
According to react-navigation docs we can use as below
componentDidMount () {
this.unsubscribe= this.props.navigation.addListener('focus', () => {
//Will execute when screen is focused
})
}
componentWillUnmount () {
this.unsubscribe()
}
Similar to vitosorriso`s answer but should changed didFocus to focus according to docs
You want to perform something after every time navigating to a component using drawernavigator or stacknavigator ; for this purpose NavigationEvents fits better than componentDidMount .
import {NavigationEvents} from "react-navigation";
<NavigationEvents onDidFocus={()=>alert("Hello, I'm focused!")} />
Note : If you want to do the task every time after focusing on a component using drawer navigation or stack navigation then using NavigationEvents is better idea. But if you want to do the task once then you need to use componenetDidMount .
//na pagina que você quer voltar
import {NavigationEvents} from 'react-navigation';
async atualizarEstado() {
this.props.navigation.setParams({
number: await AsyncStorage.getItem('count'),
});}
render() {
return (
<View style={styles.container}>
<NavigationEvents onDidFocus={() => this.atualizarEstado()} />
</View>
);
}
I have face this issue, the problem is when you navigate a page, the first time it call constructor, componentWillmount, render componentDidmount,
but in second time when navigate to the same page it only call render, so if you do any API call or something from componentDidmount it would not be called,
and also componentWillunmount never called.
You can use this method, if you are using react-navigation 5.x with class component, it can solve your problem.
for every class component page add this method and call this method once from the constructor
constructor(props) {
super(props);
this.state = {
...
};
...
this.navigationEventListener(); // call the function
}
navigationEventListener = () => { // add this function
let i = 0;
const initialState = this.state;
this.props.navigation.addListener('focus', () => {
if (i > 0) {
this.setState(initialState, () => {
//this.UNSAFE_componentWillMount(); // call componentWillMount
this.componentDidMount(); // call componentDidMount
});
}
});
this.props.navigation.addListener('blur', () => {
this.componentWillUnmount(); //call componentWillUnmount
++i;
});
}
https://reactnavigation.org/docs/navigation-events/
useEffect(() => {
const unsubscribe = props.navigation.addListener('focus', () => {
// do something
// Your apiCall();
});
return unsubscribe;
}, [props.navigation]);
In React, componentDidMount is called only when component is mounted.I think what you are trying to do is call your API on going back in StackNavigator. You can pass a callback function as parameter when you call navigate like this on Parent Screen:
navigate("Screen", {
onNavigateBack: this.handleOnNavigateBack
});
handleOnNavigateBack = () => {//do something};
And on Child Screen
this.props.navigation.state.params.onNavigateBack();
this.props.navigation.goBack();

React Navigation - Navigating between different StackNavigators with protected view

Sorry if there is an obvious solution to this but I don't find the documentation for react-navigation to be very clear which is frustrating as it's now being presented as the official react-native navigation library.
In my index file I call asynstorage to retrieve an access token generated on login. Then I want to either return a protected stack if logged in or the welcome stack if not.
render() {
if (this.state.isLoggedin === true) {
return <Protected />;
}
else {
return <Root />;
}
}
Here is my router
export const Root = StackNavigator({
Welcome: { screen: Welcome },
Login: { screen: Login },
Register: { screen: Register }
});
export const Protected = StackNavigator({
Conversations: { screen: Conversations }
});
This is working when I reload the app. What I don't understand is how to navigate from "Root" to "Protected" on press, I don't want these routes in the same StackNavigator. Is there a way to do this or do I need to change the entire structure and have both Root and Protected under one stacknavigator?
Again sorry if this is obvious but i've built a chat app using the react native Navigator before it was deprecated and didn't find it nearly as confusing. I feel that there should be better documentation and examples of how a real world app should be structured.
you can navigation a other page with this.props.navigation.navigate('otherView'), for example review, that next example navigato to a page Chat
App.js
class ChatScreen extends React.Component {
static navigationOptions = {
title: 'Chat with Lucy',
};
render() {
return (
<View>
<Text>Chat with Lucy</Text>
</View>
);
}
}
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
const { navigate } = this.props.navigation;
return (
<View>
<Text>Hello, Chat App!</Text>
<Button
onPress={() => navigate('Chat')}
title="Chat with Lucy"
/>
</View>
);
}
}
this routes was defined, in this have Chat a where want navigate
const SimpleApp = StackNavigator({
Home: { screen: HomeScreen },
Chat: { screen: ChatScreen },
});
Warning if you are doing a login verify the subject that after being logged in the user could give back and return to the login, it would be advisable to reset the routes when logging in
this.navigation.dispatch(NavigationActions.reset(
{
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Home', params: {param: data},})
]
}));
I totally agree with you, React Navigation's documentation can be very confusing. Unforutnately Navigators don't provide a direct way to pass down props to child components. For this case, you unfortunately need to use an escape hatch, basically a common module that allows you to connect the components to each other. Instead of advising you to write this complicated component on your own, I would strongly suggest using redux. Even though it requires some boilerplate but it would allow you to connect all components to the same state, so in your main component class the isLoggedIn variable would immediately change when the Login or Register components change it using redux actions. I would suggest reading the redux documentation and getting started with it. You basically create a redux store with just the user authentication in it and go from there.

How to run code in both foreground and background in React Native - Android?

I'm doing an task for listening message (even in background) and show it on UI.
I have done in listening messages real time. But if the app is in background, it doesn't receive messages in that time.
I ask for any ideas, helps.
I searched HeadlessJS, react-native-background-task but not work for me.
Actually, i want to run this app even in Background and Foreground.
Thank you for advanced.
It's my code until now
index.android.js
//Import libraries
import React, { Component } from 'react';
import {
AppRegistry,
Text
} from 'react-native';
import SmsListener from 'react-native-android-sms-listener';
//Main component
export default class Main extends Component {
state = {
lastMessage: ''
}
//Receive messages
listen = SmsListener.addListener(message => {
this.setState({ lastMessage: message.body });
console.log('message');
})
//render UI
render() {
return (
<Text> Message is: {this.state.lastMessage} </Text>
);
}
}
//Register component
AppRegistry.registerComponent('ReadMessageApp', () => Main);

Categories

Resources