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
}
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)
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)
It has three parameters:
- The Runnable interface, which cannot be null.
- An Object instance is used to cancel the runnable. It can be null.
- 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
}
}
}
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()
}
}
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)
}
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)
}
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
}
}
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>
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!
Top comments (0)