Locking a React Native App After an Amount of Time - react-native

I am working on a React Native application which shows a pincode screen upon login and each time the app is started (no longer active in the background). I would like to display this pincode screen each time a user leaves the app in the background longer than 5 minutes. This app will be deployed on Android and iOS.
Looking at search results shows me options for third-party libraries that may offer this functionality. Is there a way to do this without needing to resort to third-party tooling?

You could try to implement AppState change event
AppState.addEventListener('change', onAppStateChange);
let lastTimestamp = 0;
function onAppStateChange(state) {
if (state === 'background') {
// Store current time
lastTimestamp = Date.now();
} else if (state === 'active') {
if (Date.now - lastTimestamp > 5 * 60 * 1000) {
// Show you pin code screen
}
}
}
// Remember to remove listener when you don't need it
AppState.removeEventListener('change', onAppStateChange);

You can use PanResponder
import PanResponder from react-native
Add below inside your constructor
this. panResponser = PanResponder.create({
onStartShouldSetPanResponderCapture: () => {
let now = new Date().getTime();
if (now < your_desired_expiry_time) {
let newTime = new Date().getTime() + your_logout_time_in_milliseconds;
//now you can update your expiry time with newTime
}
else {
//go back to login
}
}
})
Usage
<View {...this.panResponser.panHandlers}>
...your other views
</View>

Related

idle-vue plugin for idle state issue

I am using idle-vue plugin to see if any logged-in user is inactive for more than 10 minutes and if yes then logged them out after displaying a pop-up message.
I have followed this https://medium.com/js-dojo/how-to-set-timer-idle-in-vue-1f4b57beb886 site to implement the code. But here I want to let user to decide whether he wants to continue the session or to logout from active session. and I want that option on idle-dialog page. But as soon as the popup displays and I move cursor to click the option, the popup disappears as the idleVue value becomes false because I as a user has made any activity.
Can anyone please suggest how to achieve this functionality with idle-vue plugin or do I need to use any other plugin?
The idleness of this plugin will break when you do any activity as the mouse moves, keys down, mouse down, and touches start. So as the popup will close when you try to move the mouse.
Reversing the logic can help here.
For example, do not open the popup when the idle time reaches but open it when the user makes any activity after an inactive period.
In another word, do not open the popup instantly after idle time expires but open it when the user comes after long inactivity. In that way, the popup will be visible always and the user will come to know that he has been idle for so long, and now he can make a choice to log out or continue the session.
idle-vue library has two hooks, onIdle and onActive. You can use onActive hook to open the popup.
onActive() {
// Open the dialog when the user becomes active after an inactive period.
this.idle_dialog = true;
},
Now, you can allow users to choose options from that dialog whether to log out or to continue.
Using VueUse's useIdle (with Swal alerts - but can be done with other components). The alert shows up a few minutes before logging you out so you have time to react;
<script setup lang="ts">
import Swal from "sweetalert2";
import { useIdle, useTimestamp, watchThrottled } from "#vueuse/core";
...
const EXTEND_MESSAGE_TIME = 60 * 13; //13 minutes
const IDLE_TIMEOUT = 60 * 15; //15 minutes
const now = useTimestamp();
const { idle, lastActive } = useIdle(IDLE_TIMEOUT);
const authStore = useAuthStore();
const router = useRouter();
const idleAlertShowing = ref<boolean>(false);
let idleTimePassed, idleTimeLeft;
const idledFor = computed(() => {
return Math.floor((now.value - lastActive.value) / 1000);
});
const formatTime = (time: number) => {
const minutes = Math.floor(time / 60);
const seconds = time - minutes * 60;
return `${minutes > 0 ? `${minutes} minutes and ` : ""}${seconds} second${
seconds > 1 ? "s" : ""
}`;
};
...
watchThrottled(
idledFor,
(newValue) => {
if (newValue === EXTEND_MESSAGE_TIME) {
Swal.fire({
html: `You have been inactive for <span id='swal-idle-time-passed'>${formatTime(
newValue
)}</span> and you will be timed out in <span id='swal-idle-time-left'>${formatTime(
IDLE_TIMEOUT - newValue
)}</span>.`,
showCancelButton: false,
showConfirmButton: false
});
idleAlertShowing.value = true;
idleTimePassed = document.querySelector("#swal-idle-time-passed");
idleTimeLeft = document.querySelector("#swal-idle-time-left");
} else if (newValue >= IDLE_TIMEOUT) {
authStore.logout();
router.push({ name: "sign-in" });
if (Swal.isVisible()) {
idleAlertShowing.value = false;
Swal.close();
}
} else if (newValue > EXTEND_MESSAGE_TIME && idle.value === true) {
if (idleTimePassed && idleTimeLeft) {
idleTimePassed.innerText = formatTime(newValue);
idleTimeLeft.innerText = formatTime(IDLE_TIMEOUT - newValue);
}
} else if (newValue <= 0) {
if (idleAlertShowing.value === true && Swal.isVisible()) {
Swal.close();
}
}
},
{ throttle: 500 }
);

How to correctly use setInterval for multiple functions being called repeatedly in React Native

I am building a simple app in React Native that aims to flash different colors on the screen at certain time intervals. My implementation is as follows:
useEffect(() => {
var blinkOnValue;
var blinkOffValue;
function blinkOn() {
const colorAndWord = getRandomColor(colorArray);
setBackground(colorAndWord.color);
}
function blinkOff() {
setBackground('#F3F3F3');
}
if (strobeStart) {
if (on) {
blinkOnValue = setInterval(() => {
blinkOn();
setOn(false);
}, info.length * 1000);
} else {
blinkOffValue = setInterval(() => {
blinkOff();
setOn(true);
}, info.delay * 1000);
}
}
return () => {
on ? clearInterval(blinkOnValue) : clearInterval(blinkOffValue);
};
}, [colorArray, info.delay, info.length, on, strobeStart]);
The blinkOn function sets the background a certain color and the blinkOff function sets the background a default light gray-ish color. These functions should alternate back and forth, blinking on and off at different intervals. For example, if info.length is 2 and info.delay is 0.5, then the color should flash on for 2 seconds and then the screen should be light gray for 0.5 seconds and repeat. However, the duration of both of the blinkOn and blinkOff are happening for the same amount of time, no matter what the two values are. Sometimes it uses the value from info.length, and sometimes it uses the value from info.delay which is also quite strange.
I think it has something to do with components mounting and unmounting correctly but honestly I am quite lost. If anyone has any advice on how to make this code consistently work where it flashes appropriately I would really appreciate it.
Instead of trying to time your events just right, I suggest using a single timer and computing the blink state from the current system time.
var oldState = true;
function blink() {
var ms = new Date().getTime();
var t = ms % (info.delay + info.length);
var state = (t < info.length ? true : false);
if (state == oldState) return;
if (state) {
blinkOn();
}
else
{
blinkOff();
}
oldState = state;
}
Now set a short timer to check the time and update the blink state as needed:
setInterval( () => blink(), 100 );

How to differentiate between double tap and single tap on react native?

I need to perform different action on single and double tap on a view. On double tap I need to like the image just like Instagram double tap experience. On single tap I need to open a modal.
For double tap I have used TapGestureHandler which works perfect
<TapGestureHandler
ref={this.doubleTapRef}
maxDelayMs={200}
onHandlerStateChange={this.onProductImageDoubleTap}
numberOfTaps={2}
>
<SomeChildComponent ...
But when I add any Touchable to detect single tap in the
<TouchableWithoutFeedback onPress={this.imageTapped}>
on double tapping the this.imageTapped function is called twice along with this.onProductImageDoubleTap. Is there any way to cancel tap on touchable when two taps are done is quick succession
The best solution is not using state, since setting state is asynchronous.Works like a charm for me on android !
let lastPress = 0;
const functionalComp = () => {
const onDoublePress = () => {
const time = new Date().getTime();
const delta = time - lastPress;
const DOUBLE_PRESS_DELAY = 400;
if (delta < DOUBLE_PRESS_DELAY) {
// Success double press
console.log('double press');
}
lastPress = time;
};
return <View
onStartShouldSetResponder =
{(evt) => onDoublePress()}>
</View>
}
2022 update
This is a performant native solution without any JS thread blocking calculation!
Many more tips here
const tap = Gesture.Tap()
.numberOfTaps(2)
.onStart(() => {
console.log('Yay, double tap!');
});
return (
<GestureDetector gesture={tap}>
{children}
</GestureDetector>
);
The best solution is use react-native-gesture-handler
https://github.com/software-mansion/react-native-gesture-handler
Here is my solution -
import {State, TapGestureHandler} from 'react-native-gesture-handler';
export const DoubleTap = ({children}: any) => {
const doubleTapRef = useRef(null);
const onSingleTapEvent = (event: any) => {
if (event.nativeEvent.state === State.ACTIVE) {
console.log("single tap 1");
}
};
const onDoubleTapEvent = (event: any) => {
if (event.nativeEvent.state === State.ACTIVE) {
console.log("double tap 1");
}
};
return (
<TapGestureHandler
onHandlerStateChange={onSingleTapEvent}
waitFor={doubleTapRef}>
<TapGestureHandler
ref={doubleTapRef}
onHandlerStateChange={onDoubleTapEvent}
numberOfTaps={2}>
{children}
</TapGestureHandler>
</TapGestureHandler>
);
};
Now we will wrap the component where we need to detect the double and single tap : -
<DoubleTap>
<View>
...some view and text
</View>
</DoubleTap>
The package react-native-double-tap seems to be what you are looking for.
since you are asking on handling one tap and double tap, here's a simple code i think should covered your issue
Untested
first defined clickCount:0 in state:
state={clickCount:0, //another state}
then create a function with setTimeout to handling if user tapping once or two times:
handlingTap(){
this.state.clickCount==1 ?
//user tap twice so run this.onProductImageDoubleTap()
this.onProductImageDoubleTap :
//user tap once so run this.imageTapped with setTimeout and setState
//i set 1 sec for double tap, so if user tap twice more than 1 sec, it's count as one tap
this.setState({clickCount:1}, ()=>{
setTimeout(()=>{
this.setState({clickCount:0})
this.imageTapped()
}, 1000)
})
}
<TouchableWithoutFeedback onPress={this.handlingTap()}/>
just used TouchableWithoutFeedback instead of TapGestureHandler
With hooks:
const [lastPressed, setLastPressed] = useState(0);
const handlePress = useCallback(() => {
const time = new Date().getTime();
const delta = time - lastPressed;
setLastPressed(time);
if (lastPressed) {
if (delta < DOUBLE_PRESS_DELAY) {
console.log('double press');
} else {
console.log('single press');
}
}
}, [lastPressed]);
I have modified flix's answer into this. By this way, you can catch one and double click separately. I also changed debounce into 300ms which is fairly well for one and double click.
state={clickCount:0, //another state}
Binding context into the handlingTap method
constructor(props){
super(props)
this.handlingTap = this.handlingTap.bind(this)
}
With this function you can catch them separately
handlingTap() {
this.state.clickCount === 1
? this.doubleClick() // This catches double click
: this.setState(state => ({ clickCount: state.clickCount + 1 }), () => {
setTimeout(() => {
if (this.state.clickCount !== 2) {
this.oneClick() // this catches one click
}
this.setState({ clickCount: 0 })
}, 300)
})
}
In the button you can use this way
<TouchableWithoutFeedback onPress={this.handlingTap}></TouchableWithoutFeedback>

React Native : How can I get gestures onTouchStart, onTouchEnd and onTouchMove event positions ( like X and Y Coordinations) in Android

How would I get the coordinates of my onTouchEnd event. So I touch or move figure anywhere within the display and I can retrieve the X, Y positioning of where it happened. onResponderRelease is not triggered in a onTouchEnd on Android,
I've included example implementations for all of the gesture response event handlers, but have commented out most of them on my View to just provide the basic functionality: subscribing to all touch and move events
pseudo code would look like this :
<View
style={this.props.style}
ref={this._saveRef}
onStartShouldSetResponder={(event) => {
return this.handleDoubleTap({nativeEvent:
event.nativeEvent});
}}
onResponderGrant={this._onTouchStart}
onResponderMove={this._onTouchMove}
onResponderRelease={this._onTouchEnd}
onResponderTerminate={this._onTouchEnd} // When
onResponderRelease can't call by some reason
>
{this.props.children}
</View>
Responder Event Handler Methods :
if (this.isDoubleTap) {
return false;
}
this.context.setScroll && this.context.setScroll(false);
const currentTouchTimeStamp = Date.now();
this._prevTouchInfo = {
prevTouchX: nativeEvent.pageX,
prevTouchY: nativeEvent.pageY,
prevTouchTimeStamp: currentTouchTimeStamp
};
this.props.onStart(nativeEvent);
};
_onTouchMove = ({nativeEvent}) => {
if (nativeEvent.touches.length <= 1) {
if (this.isDoubleTap) {
return false;
}
const self = this;
const gesture = {
x0: this._prevTouchInfo.prevTouchX,
x1: nativeEvent.pageX,
y0: this._prevTouchInfo.prevTouchY,
y1: nativeEvent.pageY,
dx: nativeEvent.pageX - this._prevTouchInfo.prevTouchX,
dy: nativeEvent.pageY - this._prevTouchInfo.prevTouchY
};
InteractionManager.runAfterInteractions(function () {
self.props.onMove(nativeEvent, gesture);
});
}
};
_onTouchEnd = ({nativeEvent}) => {
nativeEvent.touches.length === 0 && this.context.setScroll && this.context.setScroll(true);
this.props.onEnd(nativeEvent);
};
}
I am Developing an Application in React native.Actually when i am touch on Particular Position On view, getting the Corresponding x and y co-ordinates. App UI would look like this:
If this is still not enough functionality for android (eg. if you need multi-touch info), refer to PanResponde

React Native Clock - setting a timer for a long period of time

I am trying to create an app with React Native that needs to show a clock on screen (only minutes need to update correct, not seconds). The app may be on screen (visible) for multiple minutes.
Is there a correct way to do this, as so far every way I have tried brings up 'Setting a timer for a long period of time, i.e. multiple minutes, is a performance and correctness issue on Android...'.
How can I accomplish a simple clock with correctness and minimal effect on battery etc.
The clock works fine, I just get the warning. I've tried various methods including regular setTimeout/Interval and this.setTimeout, also react-native-background-timer.
I also only need to update the clock when this screen comes back into view so I cancel the timer on componentDidUnmount. The clock only needs to update on minutes, not seconds, and doesn't need to be 100% accurate i.e. a small delay until the minutes updates is fine.
Thanks!
Code:
import React, {Component} from 'react';
import {View, Image, AppState} from 'react-native';
import {Text} from 'react-native';
import Styles from '../../styles/Styles';
import BackgroundTimer from 'react-native-background-timer';
/*
Digital Clock
*/
export default class UIDigitalClock extends Component {
// Render -----------------
render() {
var label = this.formatAMPM(this.state.time);
return (
<Text style={[Styles.h0,{fontSize:80,opacity:0.7}]}>{label}</Text>
);
}
formatAMPM(date) {
var hours = date.getHours();
var minutes = date.getMinutes();
var ampm = hours >= 12 ? 'pm' : 'am';
hours = hours % 12;
hours = hours ? hours : 12; // the hour '0' should be '12'
minutes = minutes < 10 ? '0'+minutes : minutes;
var strTime = hours + ':' + minutes;
return strTime;
}
//--------------------------------
constructor(props) {
super(props);
this.state = {
time: new Date()
}
}
timer = null;
componentWillMount() {
AppState.addEventListener('change', this.handleAppStateChange);
console.log('Digital Clock - Mount - Start');
this.startTimer();
}
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange);
this.stopTimer();
}
startTimer() {
return;
if (this.timer>0) return; // Already started
this.timer = BackgroundTimer.setInterval(() => {
// this will be executed every 200 ms
// even when app is the the background
this.updateTime();
}, 1000);
}
stopTimer() {
console.log("Digital Clock - stop ");
if (this.timer>0) {
BackgroundTimer.clearInterval(this.timer);
this.timer = -1;
}
}
handleAppStateChange = (appState) => {
console.log("Digital Clock app state: "+appState);
if (appState=='background') {
console.log('Digital Clock - App in Background - Stop');
this.stopTimer();
} else if (appState=='active') {
console.log('Digital Clock - App is Active - Start');
this.startTimer();
}
}
updateTime() {
let time = this.state.time;
let newTime = new Date();
// TODO - check if the app is in the foreground
console.log('Digital Clock - Tic ');
// Only update the render when the time changes...
if (!time || (time.getMinutes() != newTime.getMinutes() || time.getHours() != newTime.getHours())) {
console.log('Digital Clock - Time changed (mins/hours) - updating...');
this.setState({time: newTime});
}
}
}
Take a look at Later.js. You could set a schedule to fire every minutes
const schedule = later.parse.text('every 1 mins');
later.date.localTime();
later.setTimeout(<your function>, schedule);
Maybe this issue here can help people:
https://stackoverflow.com/a/49886834/12056841
This bug can't be fixed here we can only work around the error there are workarounds available that disable the warning