I have to make a react-native app (not using expo nor hooks) that can login into a user, read some simple info and then logout through a logout button or automatically due to inactivity.
I have no issues with the login, setting the timer, nor the logout button, however I have no idea of how to detect 'inactivity', is this posible with states? and how exactly?
General concensus seems to be to use PanResponder:
get user inactivity in react native
Check for Inactivity in a React Native App
state = {};
_lastInteraction = new Date();
_panResponder = {};
componentWillMount() {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: this.handleStartShouldSetPanResponder,
onMoveShouldSetPanResponder: this.handleMoveShouldSetPanResponder,
onStartShouldSetPanResponderCapture: () => false,
onMoveShouldSetPanResponderCapture: () => false,
onPanResponderTerminationRequest: () => true,
onShouldBlockNativeResponder: () => false,
});
this._maybeStartWatchingForInactivity();
}
_maybeStartWatchingForInactivity = () => {
if (this._inactivityTimer) {
return;
}
this._inactivityTimer = setInterval(() => {
if (
new Date() - this._lastInteraction >= TIME_TO_WAIT_FOR_INACTIVITY_MS
) {
this._setIsInactive();
}
}, INACTIVITY_CHECK_INTERVAL_MS);
};
// NOTE: you almost certainly want to throttle this so it only fires
// every second or so!
_setIsActive = () => {
this._lastInteraction = new Date();
if (this.state.timeWentInactive) {
this.setState({ timeWentInactive: null });
}
this._maybeStartWatchingForInactivity();
};
_setIsInactive = () => {
this.setState({ timeWentInactive: new Date() });
clearInterval(this._inactivityTimer);
this._inactivityTimer = null;
};
render() {
return (
<View
style={styles.container}
collapsable={false}
{...this._panResponder.panHandlers}>
<Text style={styles.paragraph}>
Put your app here
{' '}
{this.state.timeWentInactive &&
`(inactive at: ${this.state.timeWentInactive})`}
</Text>
<Button
title="Here is a button for some reason"
onPress={() => alert('hi')}
/>
</View>
);
You can use import AsyncStorage from '#react-native-async-storage/async-storage';
So basically, whenever user visits the app, you can store the time in which user logged in.
like this
const storeData = async (value) => {
try {
await AsyncStorage.setItem('#last_visited', new Date().toString())
} catch (e) {
// saving error
}
}
and then when user again comes back to visit the app, you can check for the difference in that time and the time stored in Async storage.
first
const getData = async () => {
try {
const value = await AsyncStorage.getItem('#last_visited')
if(value !== null) {
if(new Date() - value > 5){
// here check if time diff is what as per you is inactive then logout user
// for example ive kept 5 hours
logout()
}
// value previously stored
}
} catch(e) {
// error reading value
}
}
Hope it helps. feel free for doubts
Related
Hello i want to make an inactivity log out for my app, so if the user doesn't do anything on the app for 3 minutes, the app will comeback to the login screen.
I'm using expo, react native navigation V6, and functional components.
i haven't been able to figure how to do it. please help.
I think i was able to do that on an app that i made 1 year ago, i think this code can you help you.
const ManageExpenses = ({ route, navigation }) => {
const [time, setTime] = useState(0);
useEffect(() => {
let mounted = true;
if (mounted) {
tick();
}
return () => mounted = false;
}, []);
function tick() {
let timer = setInterval(() => {
setTime((prevTime) => (prevTime = prevTime + 1));
}, 1000);
}
if (time >= 10) {
navigation.goBack();
}
function pressHandler() {
setTime(0)
}
return (
<Pressable onPress={pressHandler} style={styles.container}>
</Pressable>
);
export default ManageExpenses;
const styles = StyleSheet.create({
container: {
flex: 1,
}
})
I created a Pressable component around the entire screen and it redefine the time state when the user press the screen. I hope this will help you!
I'm getting the time one time. sometimes I didn't get that too. I need the background time. once if I started the fetching background button it should give the time in the console until I stop the fetch background. I used expo background fetch and task manager to fetch the background time . I'm facing a problem it fetching time continuously so please help me with this coding. documentation is in expo documentation.
import * as BackgroundFetch from 'expo-background-fetch';
import * as TaskManager from 'expo-task-manager';
const BACKGROUND_FETCH_TASK = 'background-fetch1';
const now = Date.now();
console.log(`Got background fetch call at**** date: ${new Date(now).toISOString()}`);
// Be sure to return the successful result type!
return BackgroundFetch.Result.NewData;
});
async function registerBackgroundFetchAsync() {
const now = Date.now();
console.log(`Registered**** date: ${new Date(now).toISOString()}`);
console.log(" registered ");
return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
minimumInterval: 60 * 15, // 30 sec
stopOnTerminate: false, // android only,
startOnBoot: true, // android only
});
}
async function unregisterBackgroundFetchAsync() {
console.log("un registered ");
const now = Date.now();
console.log(`Un registered fetch call at**** date: ${new Date(now).toISOString()}`);
return BackgroundFetch.unregisterTaskAsync(BACKGROUND_FETCH_TASK);
}
const TrackScreen = ({ navigation }) => {
const { state, clearError, fetchContactsforTrack } = useContext(TrackContext);
const [isRegistered, setIsRegistered] = useState(false);
const [status, setStatus] =useState(BackgroundFetch.Status);
// Clear error if any
useEffect(() => {
checkStatusAsync();
const unsubscribe = navigation.addListener('focus', () => {
clearError();
});
return unsubscribe;
}, [navigation, clearError]);
/* Stop the backgroud tracking */
const stopTracking=()=>{
}
/* Stop the backgroud tracking */
const startTracking=(track)=>{
var trackdata= fetchContactsforTrack(track)
}
const checkStatusAsync = async () => {
const status = await BackgroundFetch.getStatusAsync();
const isRegistered = await TaskManager.isTaskRegisteredAsync(BACKGROUND_FETCH_TASK);
const now = Date.now();
console.log(`Checking statuscall at**** date: ${new Date(now).toISOString()}`);
console.log("-------------"+status);
console.log("-------------"+isRegistered);
console.log("-------------"+JSON.stringify(BackgroundFetch));
setStatus(status);
setIsRegistered(isRegistered);
};
const toggleFetchTask = async () => {
if (isRegistered) {
await unregisterBackgroundFetchAsync();
} else {
await registerBackgroundFetchAsync();
await BackgroundFetch.setMinimumIntervalAsync(.5);
}
checkStatusAsync();
};
return (
<View >
<View >
<Text>
Background fetch status:{' '}
<Text >{status ? BackgroundFetch.Status[status] : null}</Text>
</Text>
<Text>
Background fetch task name:{' '}
<Text >
{isRegistered ? BACKGROUND_FETCH_TASK : 'Not registered yet!'}
</Text>
</Text>
</View>
<View ></View>
<Button
title={isRegistered ? 'Unregister BackgroundFetch task' : 'Register BackgroundFetch task'}
onPress={toggleFetchTask}
/>
</View>
);
this is my code
Your function might not run immediately after a second or so. This is because the minimum interval of time is 15 minutes. Android automatically switches it to 15 minutes if the value of the interval is less than the minimum.
Maybe this would help - https://proandroiddev.com/android-restrictions-you-may-encounter-during-development-process-c39ede513813
I have set up a store function
export const storeData = async text => {
try {
await AsyncStorage.getItem("notes")
.then((notes) => {
const noteList = notes ? JSON.parse(notes) : [];
noteList.push(text);
AsyncStorage.setItem('notes', JSON.stringify(noteList));
});
} catch (error) {
console.log("error saving" + error);
}
};
When calling from the header back button it works as intended
navigation.setOptions({
headerLeft: () => (
<HeaderBackButton onPress={() => {
storeData(text).then(() => {
navigation.goBack();
}
}} />
)
});
But when using it from the hardware back button it gives me an "unhandled promise rejection, undefined is not an object. evaluating _this.navigation".
useEffect(() => {
const backHandler = BackHandler.addEventListener("hardwareBackPress", () => {
storeData(text).then(() => {
this.navigation.goBack();
});
});
return () => backHandler.remove();
}, [text]);
Can anyone see what might cause this behaviour?
replace this by props. thiskey word is used mainly in class components here i its a functional components so navigation is reached by props.navigation
The full code would look like
function EditNoteScreen({ navigation }) {
const [text, setText] = useState("");
const backAction = () => {
storeData(text).then(() => {
Keyboard.dismiss();
navigation.goBack();
});
}
useEffect(() => {
const backHandler = BackHandler.addEventListener("hardwareBackPress", () => {
backAction();
});
navigation.setOptions({
headerLeft: () => (
<HeaderBackButton onPress={() => {
backAction();
}} />
)
});
return () => backHandler.remove();
}, [text]);
If I simply have my storage function run with the hardware back press the code will work and the hardware back buttons default behavior will take me back, but then the new item will not show up until refreshed, which is why i want the back behavior delayed until saving is done.
One way to ignore this would simply be to update the flatlist again on state change, but I would rather have the information there from the refresh rather then popping in.
I implement a very simple list that calls a server that returns a page containing books.Each book has a title, author, id, numberOfPages, and price). I use a Flat List in order to have infinite scrolling and it does its job very well two times in a row (it loads the first three pages) but later it doesn't trigger the handler anymore.
Initially it worked very well by fetching all available pages, but it stopped working properly after I added that extra check in local storage. If a page is available in local storage and it has been there no longer than 5 seconds I don't fetch the data from the server, instead I use the page that is cached. Of course, if there is no available page or it is too old I fetch it from the server and after I save it in local storage.(Something went wrong after adding this behavior related to local storage.)
Here is my component:
export class BooksList extends Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 0
};
}
async storePage(page, currentTime) {
try {
page.currentTime = currentTime;
await AsyncStorage.setItem(`page${page.page}`, JSON.stringify(page));
} catch (error) {
console.log(error);
}
}
subscribeToStore = () => {
const { store } = this.props;
this.unsubsribe = store.subscribe(() => {
try {
const { isLoading, page, issue } = store.getState().books;
if (!issue && !isLoading && page) {
this.setState({
isLoading,
books: (this.state.books ?
this.state.books.concat(page.content) :
page.content),
issue
}, () => this.storePage(page, new Date()));
}
} catch (error) {
console.log(error);
}
});
}
componentDidMount() {
this.subscribeToStore();
// this.getBooks();
this.loadNextPage();
}
componentWillUnmount() {
this.unsubsribe();
}
loadNextPage = () => {
this.setState({ pageNumber: this.state.pageNumber + 1 },
async () => {
let localPage = await AsyncStorage.getItem(`page${this.state.pageNumber}`);
let pageParsed = JSON.parse(localPage);
if (localPage && (new Date().getTime() - localPage.currentTime) < 5000) {
this.setState({
books: (
this.state.books ?
this.state.books.concat(pageParsed.content) :
page.content),
isLoading: false,
issue: null
});
} else {
const { token, store } = this.props;
store.dispatch(fetchBooks(token, this.state.pageNumber));
}
});
}
render() {
const { isLoading, issue, books } = this.state;
return (
<View style={{ flex: 1 }}>
<ActivityIndicator animating={isLoading} size='large' />
{issue && <Text>issue</Text>}
{books && <FlatList
data={books}
keyExtractor={book => book.id.toString()}
renderItem={this.renderItem}
renderItem={({ item }) => (
<BookView key={item.id} title={item.title} author={item.author}
pagesNumber={item.pagesNumber} />
)}
onEndReachedThreshold={0}
onEndReached={this.loadNextPage}
/>}
</View>
)
}
}
In the beginning the pageNumber available in the state of the component is 0, so the first time when I load the first page from the server it will be incremented before the rest call.
And here is the action fetchBooks(token, pageNumber):
export const fetchBooks = (token, pageNumber) => dispatch => {
dispatch({ type: LOAD_STARTED });
fetch(`${httpApiUrl}/books?pageNumber=${pageNumber}`, {
headers: {
'Authorization': token
}
})
.then(page => page.json())
.then(pageJson => dispatch({ type: LOAD_SUCCEDED, payload: pageJson }))
.catch(issue => dispatch({ type: LOAD_FAILED, issue }));
}
Thank you!
I am trying to use react navigation authentication flow to manage the login screen if the user is logged in or not. But now I got stuck in AsyncStorage. So while the user is not logged in I presume that componentWillMount will wait until the user will input the credentials, tap the login button, receive the user_id from API call and then try again. For me now it is calling what in the beginning which is fine but then I have to exit from app and go back to get the dashboard rendered. Any solution?
This is my code from App.js where I'm creating the routes as well. Also I am loading redux map on bottom.
export const createRootNavigator = (signedIn = false) => {
return SwitchNavigator(
{
SignedIn: {
screen: SignedIn
},
SignedOut: {
screen: SignedOut
}
},
{
initialRouteName: signedIn ? "SignedIn" : "SignedOut"
}
);
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
signedIn: false,
checkedSignIn: false
};
}
async componentWillMount() {
await isSignedIn()
.then(res => this.setState({ signedIn: res, checkedSignIn: true }))
.catch(err => alert("An error occurred"));
}
render() {
const { checkedSignIn, signedIn } = this.state;
// If we haven't checked AsyncStorage yet, don't render anything (better ways to do this)
if (!checkedSignIn) {
return null;
}
const Layout = createRootNavigator(signedIn);
return (
<SafeAreaView style={styles.safeArea}>
<View style={{flex: 1, backgroundColor: '#ffffff'}}>
<StatusBar barStyle="light-content"/>
<Layout />
<AlertContainer/>
</View>
</SafeAreaView>
)
}
};
And here is the Auth.js where I am waiting for the user_key.
export let USER_KEY = 'myKey';
export const onSignIn = async () => { await AsyncStorage.setItem(USER_KEY, 'true') };
export const onSignOut = async () => { await AsyncStorage.removeItem(USER_KEY) };
export const isSignedIn = () => {
return new Promise((resolve, reject) => {
AsyncStorage.getItem(USER_KEY)
.then(res => {
if (res !== null) {
// console.log('true')
resolve(true);
} else {
resolve(false);
// console.log('false')
}
})
.catch(err => reject(err));
});
};
A solution would be to make use of Splashscreen. You can add a splashscreen to the App. While Splashscreen is being displayed, check if user exists in Asyncstorage, if they do, navigate user to the Dashboard/Homescreen and if asynstorage responds null, navigate user to the Login page. Once Navigation is complete, you can hide the splashscreen. Checkout this package in npmjs for Splashscreen setup react-native-splash-screen