The react-native-background-timer
We install by:
yarn add react-native-background-timer @types/react-native-background-timer
Use Case: Check Idling
In my app I have a third party voice-conferencing service called AgoraRTC which is not free. I want to kick user off the service if he/she is not actively using our app. Our criterion is: our app is in background
state for 1 minute.
AppState Investigation
We can invertigate the changes in appState
by using
useEffect(() => { const subscription = AppState.addEventListener("change", (nextAppState) => { console.log(nextAppState); }); });
Behaviour:
- Switch to Another app. appState:
active
>inactive
>background
- Lock Screen. appState:
active
>inactive
> ...
By using setInterval we can check "lock screen" up to inactive state, and our interval stop working once we turn our screen off.
Alternatively, by using
import BackgroundTimer from "react-native-background-timer"; useEffect(() => { BackgroundTimer.setInterval(() => { console.log(appStateRef.current); }); }); useEffect(() => { const subscription = AppState.addEventListener("change", (nextAppState) => { appStateRef.current = nextAppState; }); });
our interval can continue running in the background, and we come to the conclusion that
- Lock Screen. appState:
active
>inactive
>background
We therefore come up with the following setTimeout
to trigger inactiveAction
(which kicks users out of the service).
Finalize to a Hook
import { useEffect, useRef } from 'react'; import { AppState, AppStateStatus } from 'react-native'; import BackgroundTimer from 'react-native-background-timer'; export default ({ inactiveAction = () => { }, inactiveTimeout = 1000 * 60 }: { inactiveAction?: () => void, inactiveTimeout?: number }) => { const appStateRef = useRef<AppStateStatus | null>(null); const inactiveTimeoutRef = useRef<ReturnType<typeof BackgroundTimer.setTimeout> | null>(null); const timeoutIsSet = useRef(false); const startInactiveTimeout = () => { inactiveTimeoutRef.current = BackgroundTimer.setTimeout(() => { if (appStateRef.current === "inactive" || appStateRef.current === "background") { inactiveAction(); if (inactiveTimeoutRef.current) { timeoutIsSet.current = false; BackgroundTimer.clearTimeout(inactiveTimeoutRef.current); } } }, inactiveTimeout); } useEffect(() => { const subscription = AppState.addEventListener("change", nextAppState => { appStateRef.current = nextAppState; if (nextAppState === "inactive" && !timeoutIsSet.current) { startInactiveTimeout(); timeoutIsSet.current = true; } if (nextAppState === "active") { if (inactiveTimeoutRef.current) { timeoutIsSet.current = false; BackgroundTimer.clearInterval(inactiveTimeoutRef.current); } } }); return () => { subscription.remove(); } }, []); useEffect(() => { return () => { BackgroundTimer.stopBackgroundTimer(); } }, []); }
Usage
Inside a component:
const completeQuitRoom = () => { removeAgora(); }; useAppInactiveChecker({ inactiveAction: completeQuitRoom, inactiveTimeout: 1000 * 60, });