Implementing an Auto-logout Feature for Android in Kotlin

nmke

nancy

Posted on April 9, 2024

Implementing an Auto-logout Feature for Android in Kotlin

Handling sensitive data in an app may require some checks to see if the user is still active and decide whether or not to keep him/her logged in. We will use a straightforward approach: gesture detectors and the Handler class to set timeout intervals.

Let us head on straight to the concept.

Prerequisites

To follow through, you'll need to have these:

  • A basic understanding of Kotlin and programming in general (OOP).
  • Android Studio or IntellijIDEA (configured for Android development) installed and working in your machine.
  • Knowledge of building Android applications.

What we will be doing

We will be creating an app that displays a dialog asking the user if he/she is still active or not. The method for displaying the dialog will be fired after 15 seconds of inactivity. We check for the user's activeness by checking if he is tapping on the screen.

We embed our gesture detector on the whole screen and not on individual subclassed views, enabling us to detect gestures that will be the final output.

The theory of auto-logging out in our app

For any gesture detected, the following actions are done:

We first count the number of clicks done at every gesture detected on the screen. For example, if a user scrolls, clicks increase by 1. If he scrolls again, the count is incremented again, making the number of clicks to 2. The same will be done for all other gestures.

Second, the user's active status will be set to true.

Then, we start tracking the user's active status. There is a catch here. To avoid the dialog being displayed many times, we will only call the handler to the dialog display method. We achieve that by calling this method when the number of clicks is 1.

We earlier stated that we would display the dialog after 15 seconds. We split this into 10 seconds and 5 seconds. Once we start detecting inactivity after a user interacts with the app, wait for 10 seconds. After that, deliberately set the active status to false, wait again for 5 seconds, and recheck the active status.

If the user has not interacted with the screen, definitely the status will still be false. Now that it is false, we display the dialog. Otherwise, when he/she interacts with the screen, the active status will be true, so do not display the dialog. It is as simple as that.

Before we start creating the app, we will briefly look at the two 'tools' we will use:

  • The GestureDetector class contains helper methods to detect gestures such as scrolling, flinging, swiping, etc.
  • The Handler class enables us to act for a specific duration.

The GestureDetector class

To detect all common gestures, we implement the GestureDetector.OnGestureListener interface. To detect a subset of gestures, we extend the GestureDetector.SimpleOnGestureListener. We will use the first option in our application.

Let's look at this code snippet:

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.GestureDetector
import android.view.MotionEvent
import androidx.core.view.GestureDetectorCompat


class MainActivity :
    AppCompatActivity(),
    GestureDetector.OnGestureListener {

    private lateinit var ourDetector: GestureDetectorCompat

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Instantiating the gesture detector
        /*We pass in the application context and an implementation of
         GestureDetector.OnGestureListener as the arguments*/
        ourDetector = GestureDetectorCompat(this, this)

    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        return if (ourDetector.onTouchEvent(event)) {
            true
        } else {
            super.onTouchEvent(event)
        }
    }

    override fun onDown(event: MotionEvent): Boolean {
        //do something
        return true
    }

    override fun onFling(
        event1: MotionEvent,
        event2: MotionEvent,
        velocityX: Float,
        velocityY: Float
    ): Boolean {
        //do something
        return true
    }

    override fun onLongPress(event: MotionEvent) {
        //do something
    }

    override fun onScroll(
        event1: MotionEvent,
        event2: MotionEvent,
        distanceX: Float,
        distanceY: Float
    ): Boolean {
        //do something
        return true
    }

    override fun onShowPress(event: MotionEvent) {
        //do something
    }

    override fun onSingleTapUp(event: MotionEvent): Boolean {
        //do something
        return true
    }
Enter fullscreen mode Exit fullscreen mode

The first thing we note is that our Activity class extends the interface (GestureDetector.OnGestureListener). Since it is an interface, we have to use all its methods, as you can see. We override them to put in our custom code. In the onCreate() method, we instantiate the gesture detector and then pass it in the current application context and implementation of GestureDetector.OnGestureListener as the arguments.

In all the overridden methods, you will notice two similarities - they take in a MotionEvent object and return true. The MotionEvent is used to report movement events happening in the Activity. We return true to show that the detection of this event. For the onFling() and the onScroll() methods, we pass in extra parameters used to detect the position where the two gestures were performed.

They may be necessary when creating products like games and others. We will not use them for our article. For more information on the GestureDetector class, follow this link pointing to the official documentation.

The Handler class

This class is used when we want to perform a particular action like calling a method, opening an activity, sending an SMS after a specific time has elapsed.

Take this snippet as an example:

Handler(Looper.getMainLooper()).postDelayed({
    //the method(callback method) to be called
    displayDialog()
}, 5000)
Enter fullscreen mode Exit fullscreen mode

In the Handler class' constructor, we pass in a postDelayed() method from Looper's getMainLooper() method. A Looper is used to run a message loop for a thread. The getMainLooper(), as the name suggests, gets the looper running in an application's main thread.

The postDelayed() method enables a Runnable to run after a specified amount of time. The Runnable, in this case, may be a method. Check this code.

public final boolean postDelayed (Runnable outRunnable,  Object ourObject, long delayTime)
Enter fullscreen mode Exit fullscreen mode

It has three parameters:

  1. The Runnable interface, which cannot be null.
  2. An Object instance is used to cancel the runnable. It can be null.
  3. A long type that stores the time, in milliseconds, to wait before calling the runnable.

Read more about the Handler class here.

Creating the app

Now that we have looked at the theory and what we will be using in our article, we will go ahead to create our application. Choose the Empty Activity option, set your preferred name (I called mine AutoLogout), choose the language as Kotlin, and then Finish.

Modifying the MainActivity.kt file

Copy and paste the following code to your MainActivity.kt file after your package name declaration.

import android.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.GestureDetector
import android.view.MotionEvent
import androidx.core.view.GestureDetectorCompat


class MainActivity :
    AppCompatActivity(),
    GestureDetector.OnGestureListener {

    private lateinit var ourDetector: GestureDetectorCompat
    private var timeset: Long = 10000
    private var noOfClicks: Int = 0
    private var isActive: Boolean = false


    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Instantiating the gesture detector
        /*We pass in the application context and an implementation of
         GestureDetector.OnGestureListener as the arguments*/
        ourDetector = GestureDetectorCompat(this, this)

    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        return if (ourDetector.onTouchEvent(event)) {
            true
        } else {
            super.onTouchEvent(event)
        }
    }

    override fun onDown(event: MotionEvent): Boolean {
        startDetection()
        return true
    }

    override fun onFling(
        event1: MotionEvent,
        event2: MotionEvent,
        velocityX: Float,
        velocityY: Float
    ): Boolean {
        startDetection()
        return true
    }

    override fun onLongPress(event: MotionEvent) {
        startDetection()
    }

    override fun onScroll(
        event1: MotionEvent,
        event2: MotionEvent,
        distanceX: Float,
        distanceY: Float
    ): Boolean {
        startDetection()
        return true
    }

    override fun onShowPress(event: MotionEvent) {
        startDetection()
    }

    override fun onSingleTapUp(event: MotionEvent): Boolean {
        startDetection()
        return true
    }

    //a method to start detection
    fun startDetection() {
        //set the user's active status to true then increase the number of clicks by 1
        isActive = true
        noOfClicks++

        //dont listen more than once
        //simply do nothing :)
        if (noOfClicks > 1) {

        } else if (noOfClicks == 1) {
            startListener()
        }
    }

    fun startListener() {
        //check the user's activeness after a specified time in milliseconds
        Handler(Looper.getMainLooper()).postDelayed({
            //displayDialog()
            checkActiveness()
        }, timeset)
    }

    fun checkActiveness() {
        /*set the active status to false deliberately
        and wait for 5 seconds to see if it will change to true
        */
        isActive = false
        Handler(Looper.getMainLooper()).postDelayed({

            displayDialog()
        }, 5000)
    }

    fun displayDialog() {
        //if the user is still inactive, show the dialog
        /*reset the number of clicks to zero to start
        the detection  incase the dialog is dismissed*/
        if (isActive == false) {
            noOfClicks = 0
            val displayDialog = AlertDialog.Builder(this)
            displayDialog.apply {
                setTitle("Autologout")
                setMessage("It seems you have you have been away for a while. Would you like us to sign you out?")
                setPositiveButton("Yes, Sign out") { _, _ ->

                }
                setNegativeButton("Keep me in") { _, _ ->

                }
            }.create().show()
        } else {
            /*if active, set the clicks to zero also
            to allow the detection to start as the user clicks/taps the screen
             */
            noOfClicks = 0
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

After importing the required packages, we then declare the variable to hold our detector object, instantiate the values to hold the number of clicks, delay time, and the user's active status. The overridden methods are implemented as we discussed beforehand, only that we pass in a startDetection() method to each of them. Let us see what the startDetection() method does.

    //a method to start detection
    fun startDetection() {
        //set the user's active status to true then increase the number of clicks by 1
        isActive = true
        noOfClicks++

        //dont listen more than once
        //simply do nothing :)
        if (noOfClicks > 1) {

        } else if (noOfClicks == 1) {
            startListener()
        }
    }
Enter fullscreen mode Exit fullscreen mode

Here is where the inactivity detection starts. We begin by setting the user's active status to true and increasing the number of clicks by 1. A conditional check allows listening for clicks only once after the first one is passed.

Moving to the startListener() method.

    fun startListener() {
        //check the user's activeness after a specified time in milliseconds
        Handler(Looper.getMainLooper()).postDelayed({
            checkActiveness()
        }, timeset)
    }
Enter fullscreen mode Exit fullscreen mode

We call the method to check for the user's activeness after the time stored in the timeset variable(10 seconds) elapses. The method is called checkActiveness() discussed next.

    fun checkActiveness() {
        /*set the active status to false deliberately
        and wait for 5 seconds to see if it will change to true
        */
        isActive = false
        Handler(Looper.getMainLooper()).postDelayed({

            displayDialog()
        }, 5000)
    }
Enter fullscreen mode Exit fullscreen mode

After the 10 seconds, set the active status to false deliberately and wait for 5 seconds to see if it will change to true. If it's still false, display the dialog as we will see in the final method, displayDialog().

fun displayDialog() {
        //if the user is still inactive, show the dialog
        /*reset the number of clicks to zero to start
        the detection  incase the dialog is dismissed*/
        if (isActive == false) {
            noOfClicks = 0
            val displayDialog = AlertDialog.Builder(this)
            displayDialog.apply {
                setTitle("Autologout")
                setMessage("It seems you have you have been away for a while. Would you like us to sign you out?")
                setPositiveButton("Yes, Sign out") { _, _ ->

                }
                setNegativeButton("Keep me in") { _, _ ->

                }
            }.create().show()
        } else {
            /*if active, set the clicks to zero also
            to allow the detection to start as the user clicks/taps the screen
             */
            noOfClicks = 0
        }
    }
Enter fullscreen mode Exit fullscreen mode

If the user is still inactive, show the dialog and reset the number of clicks to 0 to start the detection. If the user and gestures dismiss, the dialog is detected. If active, set the clicks to 0 again to allow the detection to start as the user clicks/taps the screen.

Note that we have to do some touches on the screen for the methods to start firing.

If you do not want to wait until someone touches the screen, you can call the startDetection() method in the Activity's onCreate() method as well after instantiating the gesture detector.

Layout XML (activity_main.xml) file

This is the XML code. It contains a TextView displaying "Hello there. You will be logged out after 15 seconds".

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello there. You will be logged out after 15 seconds"
            android:id="@+id/sampleTxt"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" android:paddingLeft="15dp" android:paddingRight="15dp"/>

</androidx.constraintlayout.widget.ConstraintLayout>
Enter fullscreen mode Exit fullscreen mode

You can find the sample APK to this project here.

Run the app, do some touches, scrolls, and wait 15 seconds to checkout the feature. Also, do the same before the seconds elapse and check. Change the numbers and check again.

Further practice

Now, this was for one screen. What if you wanted to implement it on many screens? Would you rewrite the code in each Activity? Definitely, not. So get some coffee and write an inheritable(extendable) class you can call on any screen.

You can also make the function open another activity instead of the dialog. You can implement a counter for the last 5 seconds and then later do a particular action like opening a Login screen.

Conclusion

We looked at the working theory, briefly touched on the GestureDetector and Handler classes, and finally created an auto-logout app using the two classes. Hope you had a great read.

Happy coding!

💖 💪 🙅 🚩
nmke
nancy

Posted on April 9, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related