Getting a user's total screen time in a React Native app is an interesting piece of functionality I had to engineer recently. I started with hopeful pragmatism that someone else already solved this issue. Yet, after quite a bit of Google searching, there weren't any libraries out there that helped achieve this. That notion alone enticed me to put my best foot forward with a solution. It took me wrapping my head around how React Native apps are used and the lifecycle behind the AppState, to even get to an answer.

To specify a bit more, the true goal was to figure out how long someone had the app open up on their screen. There are a few approaches I thought of and some considerations that I had to make.

The first of which was when and how the info was needed. My app doesn't require it to be a live ticking piece of data. For example, if I wanted to show someone on the screen how long they had been looking at it. Rather, I needed to know the "session length" of their viewing of the app after the fact.

My application uses React Navigation. So, I started inside our main Navigation component, which I call the AppStack. This is one of the first things that gets rendered and the component that determines what else gets rendered, so it felt natural to put the logic there. 

import React from "react";
import { AppState } from "react-native";
import { differenceInSeconds } from "date-fns"
import { createStackNavigator } from "@react-navigation/stack";

const Stack = createStackNavigator();

const AppStack = () => {
  let appStartTime = new Date();
  const viewTime = React.useState(0)
  return (
      {/* ...Rest of your navigation stuff to be rendered. */}

export default AppStack;

There's a couple of things to note about the imports. I am using React, React Native, React Navigation, and date-fns libraries, so those will need to be installed. Date-fns is very optional here and used to quickly handle date calculations, in this case. While I highly recommend the package, you could just make your own "differenceInSeconds" function if you wanted.

The first thing to happen when the AppStack function is called the setting of the appStartTime variable by instantiating a new Date object. This gives us a timestamp. The reason we are using a classic variable and not React.useState is because this variable will not be rendered anywhere. We don't need the overhead of state updating to get in the way.

React.useEffect(() => {
    AppState.addEventListener("change", handleAppStateChange);
    return () => {
      AppState.removeEventListener("change", handleAppStateChange);
  }, []);

This useEffect call is added above the return section of AppStack. 

It creates an event listener that fires when the AppState changes and takes in a callback function that fires. Essentially, it will let us know when someone is moving the app from the active state to an inactive state, or vice versa.

Above the useEffect, we declare the callback it takes and what it does.

 const handleAppStateChange = (nextAppState) => {
    if (
      appState.current.match(/inactive|background/) &&
      nextAppState === "active"
    ) {
      appStartTime = new Date();
    } else {
      const viewSessionDuration = differenceInSeconds(
        new Date(),
      // you would then take the viewSessionDuration and do whatever you want with it. Save it to your local app DB, or send it off to an API. 

This handleAppStateChange callback is the meat and potatoes. The event listener passes us nextAppState, which gives us what it's intending to do next. The very first evaluation we make is if the app is coming into the foreground from being in inactive or in the background states.

If that is true, we set our startTime to a new Date object. It is a new view session after all.

Our else statement will execute when the app is leaving the active state, the opposite of the first evaluation. This is where we can figure out how long the view session was and perform some work.

In this case, I'm just saving it to a local variable. But, once we have the difference we're done. All we have to do is send that data somewhere. It could be to an API via a network request, or perhaps to a Realm database like I have running on my app.

This solution seems very simple, and it is. Hopefully it helps someone else who stumbles upon this, or inspires an even better method.