Analyzing User behaviour we can see that a lot of applications are changing the state of them from foreground to background and in reverse order.

Different states of the application on a device
Different states of the application

However, as being developers, we would like to know when our application moved to the background. It’s a very popular use case for analytics. Different analytics solutions define sessions in different ways, some solutions allow developers to define “sessions” meaning. Let’s create a simple analytics abstraction which informs us about the duration of the session which will be calculated in the following way: when we start the application, the session is also started and when the application moves to the background our session is finished. Finally, we would like to display this information in logs.

Sometime ago, Lifecycle Architecture Component moved to release and let’s try to solve our use case with Lifecycle framework. Fortunately, we can use ProcessLifecycleOwner which helps us to get information about changing the state of the application.

So, let’s start with adding dependencies for lifecycle framework. We need two dependencies, they are “lifecycle:runtime”  “lifecycle:compiler” and “lifecycle:extensions ones.

dependencies {
    ...

    implementation "androidx.lifecycle:lifecycle-runtime:2.0.0"
    implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
    annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.0.0"
}

Right now we can move to developing of our solution.

Let’s start with creating an ApplicationObserver which helps us to handle the changing state of the application and configure our small analytics solution in a proper way. So we want to know when our application moved to the background and to the foreground. We can use Lifecycle event for it:
– when application moved to background ON_STOP event will be triggered
– when application moved to foreground ON_START event will be triggered

We can create an ApplicationObserver which simplifies defining sessions for the analytics.

class ApplicationObserver(val analytics: Analytics) : LifecycleObserver {
    
    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onBackground() {

    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onForeground() {

    }
}

The next step is attaching ApplicationObserver to our application. We should register this observer in the application class. In my case, it’s a MapNotesApp class. So, we should register it inside onCreate method. We can use the following code for initialization of ProcessLifecycleOwner and registering ApplicationObserver component.

class MapNotesApp : Application() {
    override fun onCreate() {
        super.onCreate()
        ...

        val analytics = Analytics()
        analytics.addReporter(LogReporter())

        ProcessLifecycleOwner
                .get()
                .lifecycle
                .addObserver(ApplicationObserver(analytics))
    }
    ...
}

At the moment we can move to creating abstraction for our analytics. I suggest to create a simple solution which can be extended, but even this implementation will be enough for demo purpose.

Solution diagram

The Analytics class allows us to collect session information.

class Analytics {
    private var startSessionTimestamp: Long = -1
    private val reporters = mutableListOf<AnalyticsReporter>()

    fun addReporter(reporter: AnalyticsReporter) {
        reporters.add(reporter)
    }

    fun startSession() {
        startSessionTimestamp = Date().time
    }

    fun stopSession() {
        reportSession()
        sendAllEvents()
        startSessionTimestamp = -1
    }

    private fun reportSession() {
        reporters.forEach {reporter ->
        val currentTime = Date().time
        // we should check if session was started and stopped correctly
        val sessionTime = (currentTime - startSessionTimestamp) / 1000
            reporter.report("Session time: $sessionTime sec" )
        }
    }

    private fun sendAllEvents() {
        reporters.forEach {reporter ->
            reporter.sendAllEvents()
        }
    }
}

The AnalyticsReporter interface is an abstraction for all reporters, like LogReporter.

interface AnalyticsReporter {

    fun report(event: String)

    fun sendAllEvents()
}

The LogReposter is implementation for AnalyticsReporter interface. It displays all logs in the console.

class LogReporter : AnalyticsReporter {
    private val events = mutableListOf<String>()

    override fun report(event: String) {
        events.add(event)
    }

    override fun sendAllEvents() {
        events.forEach { event ->
            Log.d(this.javaClass.simpleName, event)
        }
        events.clear()
    }
}

Finally, we can update the ApplicationObserver class for starting and stopping the session.

class ApplicationObserver(val analytics: Analytics) : LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onForeground() {
        analytics.startSession()
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onBackground() {
        analytics.stopSession()
    }
}

Afterwards, we can run our application and check the log. When the application moves to the foreground, our session starts, but when the application moves to the background, we finish our session with small delay and display this information in logs.

com.alex.mapnotes D/LogAnalyticsReporter: Session time: 28 sec

The main reason for this delay is the internal implementation of ProcessLifecycleOwner and we will talk about it in a moment.

So, let’s move between different screens because the duration of our session is independent on the current activity. As you can see, we can easily handle case when the application moves from foreground and background and vice versa. Analytics it just one of the example when we need this information.

Let’s check how ProcessLifecycleOwner works under the hood. Everything starts when we add the lifecycle:runtime, lifecycle:compiler and lifecycle:extensions dependencies to the project. After building it the ProcessLifecycleOwnerInitializer will be added to AndroidManifest.xml as a provider which allows the framework to collect the information about changing states of the activities. The main helper here is ReportFragment, which sends information about changes in the lifecycle of each activity to LifecycleRegistry class. That is a hub for collecting information about the lifecycle and it can notify an interested observer about changes in the lifecycle of LifecycleOwners. However, the framework has one trick that is a delay and it is used for verification that no new activity was created, and this delay is equal to 700 milliseconds.

0 CommentsClose Comments

Leave a comment