Many of us have used the step counter on our phones while we go for walk or run. It counts the total steps that the user has walked and displays it on the screen. The other name for the step counter is a pedometer. But have you ever thought about how can an app count our steps? What is the coding behind the working of this app? Let's find the answer to these questions by making one.
What are we going to build in this article?
We will be building an application that displays the steps that the user has walked. We will use TextView in our XML file which will show the step count and a heading on the screen, and one ImageView for displaying the circle around the text. When the user will tap for a long time on the screen it will get reset to 0. A sample GIF is given below to get an idea about what we are going to do in this article.
Note that we are going to implement the application using Java/Kotlin language.
Step by Step Implementation
Step 1: Create a New Project
To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio.
Step 2: Adding permissions
Navigate to the app > manifests > AndroidManifest.xml and add the code given below in the manifest for taking the user permission for Activity Recognition.
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>Step 3: Working with the activity_main.xml file
Navigate to the app > res > layout > activity_main.xml and add the below code. Also create a drawable in app > res > drawable folder and name it as circle.xml. The code for both the files are given below.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!--Text View for the "Steps" displayed-->
<TextView
android:id="@+id/steps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Steps"
android:textColor="@color/black"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/tv_stepsTaken"
app:layout_constraintEnd_toEndOf="@+id/tv_stepsTaken"
app:layout_constraintStart_toStartOf="@+id/tv_stepsTaken" />
<!--Text View for the step count-->
<TextView
android:id="@+id/tv_stepsTaken"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textColor="@color/black"
android:textSize="100sp"
app:layout_constraintBottom_toBottomOf="@+id/imageView"
app:layout_constraintEnd_toEndOf="@+id/imageView"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView" />
<!--Image View for the circle-->
<ImageView
android:id="@+id/imageView"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="@drawable/circle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke android:color="@color/colorPrimary" android:width="3dp"/>
<size android:width="120dp" android:height="120dp"/>
</shape>
Step 4: Working with the MainActivity file
Go to the MainActivity file and refer to the following code. Below is the code for the MainActivity file. Comments are added inside the code to understand the code in more detail.
package org.geeksforgeeks.demo;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity implements SensorEventListener {
// Declare sensor and sensor manager
private SensorManager sensorManager;
private Sensor stepSensor;
// Step counting variables
private boolean running = false;
private float totalSteps = 0f;
private float previousTotalSteps = 0f;
// UI Component
private TextView stepsTakenTextView;
private static final int PERMISSION_REQUEST_ACTIVITY_RECOGNITION = 100;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Check and request permission for Activity Recognition (Android 10+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (checkSelfPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{android.Manifest.permission.ACTIVITY_RECOGNITION}, PERMISSION_REQUEST_ACTIVITY_RECOGNITION);
}
}
// Define text view for step count
stepsTakenTextView = findViewById(R.id.tv_stepsTaken);
// Define sensor and sensor manager
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
if (sensorManager != null) {
stepSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
}
// Check if sensor is available
if (stepSensor == null) {
Toast.makeText(this, "No Step Counter Sensor found!", Toast.LENGTH_LONG).show();
}
loadData();
resetSteps();
}
// Register sensor when activity starts
@Override
protected void onStart() {
super.onStart();
registerSensor();
}
// Unregister sensor when activity stops
@Override
protected void onStop() {
super.onStop();
sensorManager.unregisterListener(this);
}
// Register step counter sensor
private void registerSensor() {
if (stepSensor != null) {
sensorManager.registerListener(this, stepSensor, SensorManager.SENSOR_DELAY_UI);
Log.d("Step Counter", "Step Sensor Registered");
}
}
// Handle step counter changes
@Override
public void onSensorChanged(SensorEvent event) {
if (event == null) return;
Log.d("Step Counter", "Sensor event received: " + event.values[0]);
// Update step count
if (running) {
totalSteps = event.values[0];
int currentSteps = (int) (totalSteps - previousTotalSteps);
stepsTakenTextView.setText(String.valueOf(currentSteps));
}
}
// Handle resume event
@Override
protected void onResume() {
super.onResume();
running = true;
registerSensor();
}
// Handle pause event
@Override
protected void onPause() {
super.onPause();
running = false;
}
// Handle long press to reset steps
private void resetSteps() {
stepsTakenTextView.setOnClickListener(v ->
Toast.makeText(MainActivity.this, "Long press to reset steps", Toast.LENGTH_SHORT).show()
);
stepsTakenTextView.setOnLongClickListener(v -> {
previousTotalSteps = totalSteps;
stepsTakenTextView.setText("0");
saveData();
return true;
});
}
// Save step count
private void saveData() {
SharedPreferences sharedPreferences = getSharedPreferences("myPrefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putFloat("key1", previousTotalSteps);
editor.apply();
Log.d("Step Counter", "Steps saved: " + previousTotalSteps);
}
// Load step count
private void loadData() {
SharedPreferences sharedPreferences = getSharedPreferences("myPrefs", Context.MODE_PRIVATE);
previousTotalSteps = sharedPreferences.getFloat("key1", 0f);
Log.d("Step Counter", "Loaded steps: " + previousTotalSteps);
}
// Handle permission request results
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_ACTIVITY_RECOGNITION) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Permission Granted", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}
package org.geeksforgeeks.demo
import android.content.Context
import android.content.pm.PackageManager
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity(), SensorEventListener {
// initialize sensor and sensor manager
private lateinit var sensorManager: SensorManager
private var stepSensor: Sensor? = null
// initialize variable for step count and to give the running status
private var running = false
private var totalSteps = 0f
private var previousTotalSteps = 0f
// initialize text view for step count
private lateinit var stepsTakenTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Check and request permission for Activity Recognition (Android 10+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (checkSelfPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(android.Manifest.permission.ACTIVITY_RECOGNITION), 100)
}
}
// define text view for step count
stepsTakenTextView = findViewById(R.id.tv_stepsTaken)
// define sensor and sensor manager
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
stepSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
// check if sensor is available
if (stepSensor == null) {
Toast.makeText(this, "No Step Counter Sensor found!", Toast.LENGTH_LONG).show()
}
loadData()
resetSteps()
}
// register sensor to listen for changes
override fun onStart() {
super.onStart()
registerSensor()
}
// unregister sensor to stop listening for changes
override fun onStop() {
super.onStop()
sensorManager.unregisterListener(this)
}
// register sensor to listen for changes
private fun registerSensor() {
if (stepSensor != null) {
sensorManager.registerListener(this, stepSensor, SensorManager.SENSOR_DELAY_UI)
Log.d("Step Counter", "Step Sensor Registered")
}
}
// handle sensor event
override fun onSensorChanged(event: SensorEvent?) {
if (event == null) return
Log.d("Step Counter", "Sensor event received: ${event.values[0]}")
// update step count
if (running) {
totalSteps = event.values[0]
val currentSteps = (totalSteps - previousTotalSteps).toInt()
stepsTakenTextView.text = "$currentSteps"
}
}
// handle resume event
override fun onResume() {
super.onResume()
running = true
registerSensor()
}
// handle pause event
override fun onPause() {
super.onPause()
running = false
}
// handle long press to reset steps
private fun resetSteps() {
stepsTakenTextView.setOnClickListener {
Toast.makeText(this, "Long press to reset steps", Toast.LENGTH_SHORT).show()
}
stepsTakenTextView.setOnLongClickListener {
previousTotalSteps = totalSteps
stepsTakenTextView.text = "0"
saveData()
true
}
}
// save step count
private fun saveData() {
val sharedPreferences = getSharedPreferences("myPrefs", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.putFloat("key1", previousTotalSteps)
editor.apply()
Log.d("Step Counter", "Steps saved: $previousTotalSteps")
}
// load step count
private fun loadData() {
val sharedPreferences = getSharedPreferences("myPrefs", Context.MODE_PRIVATE)
previousTotalSteps = sharedPreferences.getFloat("key1", 0f)
Log.d("Step Counter", "Loaded steps: $previousTotalSteps")
}
// handle accuracy change
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
}
Output:
Note: We have to allow the permission required for the app by going to app settings and then enabling it.
It will not count the steps in the emulator, you have to use a real android device to test it.