While modern Android development increasingly favors Jetpack Compose for building user interfaces, many existing and legacy apps still rely on XML-based layouts. Understanding how to implement features like step trackers within an XML-based UI remains essential for maintaining and extending these applications. This post explores the process of building a step tracker UI using XML layouts and associated Kotlin/Java code to monitor and display a user’s daily steps.
Why Build a Step Tracker UI?
- Encourage Physical Activity: Providing users with a visual representation of their daily steps motivates them to stay active.
- Enhance User Engagement: Integrates fitness-related functionalities into the app, boosting user interaction and retention.
- Track Health Metrics: Forms a foundation for more comprehensive health tracking capabilities within an application.
Components of a Step Tracker UI
A typical step tracker UI includes:
- Step Count Display: Shows the current number of steps taken by the user.
- Step Goal Indicator: Visualizes the user’s progress toward their daily step goal (e.g., a progress bar).
- Activity History: Potentially displays historical step count data over days, weeks, or months (beyond the scope of this basic implementation).
Implementing a Step Tracker with XML-Based UI
Step 1: Design the UI Layout with XML
Create the layout for your step tracker using XML. This includes TextViews for displaying step counts and a ProgressBar for visualizing progress.
activity_main.xml
<?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">
<TextView
android:id="@+id/stepCountTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0 Steps"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/stepProgressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:max="10000" <!-- Example: Setting goal to 10,000 steps -->
android:progress="0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/stepCountTextView" />
</androidx.constraintlayout.widget.ConstraintLayout>
This XML file defines a basic layout with a TextView
to display the step count and a horizontal ProgressBar
to visualize the progress toward the daily step goal.
Step 2: Set Up Sensor Permissions in the Manifest
To access the step counter sensor, you must request the ACTIVITY_RECOGNITION
permission. For devices running Android 10 (API level 29) and later, this is considered a dangerous permission, so it needs to be requested at runtime as well.
AndroidManifest.xml
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
Step 3: Implement the Activity Logic (Kotlin)
In your main activity, initialize the UI elements, request necessary permissions, and set up a sensor listener to receive step count updates.
import android.Manifest
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.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
class MainActivity : AppCompatActivity(), SensorEventListener {
private var sensorManager: SensorManager? = null
private var stepSensor: Sensor? = null
private var stepCount: Int = 0
private lateinit var stepCountTextView: TextView
private lateinit var stepProgressBar: ProgressBar
private val activityRecognitionRequestCode = 100
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize UI elements
stepCountTextView = findViewById(R.id.stepCountTextView)
stepProgressBar = findViewById(R.id.stepProgressBar)
// Check and request ACTIVITY_RECOGNITION permission
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACTIVITY_RECOGNITION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACTIVITY_RECOGNITION),
activityRecognitionRequestCode
)
} else {
setupStepCounter() // If permission already granted, set up the step counter
}
} else {
setupStepCounter() // For versions older than Android 10, permission isn't runtime-required.
}
}
private fun setupStepCounter() {
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
stepSensor = sensorManager?.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
if (stepSensor == null) {
stepCountTextView.text = "Step counter sensor not available on this device."
// Consider disabling step counting functionality if no sensor
} else {
sensorManager?.registerListener(this, stepSensor, SensorManager.SENSOR_DELAY_NORMAL)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == activityRecognitionRequestCode) {
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
// Permission granted, setup the step counter
setupStepCounter()
} else {
// Permission denied. Disable functionality or inform user.
stepCountTextView.text = "Activity recognition permission denied."
}
}
}
override fun onSensorChanged(event: SensorEvent) {
if (event.sensor.type == Sensor.TYPE_STEP_COUNTER) {
stepCount = event.values[0].toInt()
updateStepCountUI()
}
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
// Handle sensor accuracy changes (optional)
}
private fun updateStepCountUI() {
stepCountTextView.text = "$stepCount Steps"
stepProgressBar.progress = stepCount
}
override fun onResume() {
super.onResume()
// Re-register listener to ensure continuous step counting even when activity pauses and resumes
stepSensor?.let {
sensorManager?.registerListener(this, it, SensorManager.SENSOR_DELAY_NORMAL)
}
}
override fun onPause() {
super.onPause()
// Unregister listener to prevent consuming resources when activity isn't active
sensorManager?.unregisterListener(this)
}
}
Key aspects of the Kotlin code:
- Permission Handling: Checks and requests runtime permission for
ACTIVITY_RECOGNITION
if required. - Sensor Setup: Initializes the
SensorManager
and obtains a reference to theTYPE_STEP_COUNTER
sensor. If the sensor doesn’t exist, provides feedback to the user. - Sensor Listener: Registers the
MainActivity
as a listener to receive step count updates. - Step Count Update: When a new step count is received from the sensor, updates the UI with the latest value.
- Lifecycle Management: Registers and unregisters the sensor listener in
onResume()
andonPause()
respectively to optimize resource usage and avoid background sensing when the activity isn’t visible.
Step 4: Testing and Refinement
Run your application on a physical Android device (emulators generally don’t emulate sensors reliably) and test the step counting functionality. Ensure steps are being accurately recorded and displayed. Refine the UI as needed based on user feedback and design considerations.
Caveats and Considerations
- Sensor Availability: Not all Android devices have a dedicated step counter sensor. The code includes a check to handle cases where the sensor is absent.
- Accuracy: Step counting accuracy varies between devices.
- Power Consumption: Continuous sensor monitoring can impact battery life. Optimizations may be necessary for long-running applications. Consider using the `TYPE_STEP_DETECTOR` which uses less power (though provides less information – it only indicates a step was taken).
- Background Processing: To track steps even when the app is in the background, consider using a foreground service (beyond the scope of this basic implementation).
- Permissions: Prior to Android 10, the ACTIVITY_RECOGNITION permission was *not* runtime required. This changed with Android 10. Also, older versions of Android may use different permission names, such as BODY_SENSORS.
Conclusion
Building a step tracker UI using XML layouts and associated Kotlin code is a valuable exercise in understanding sensor integration and UI management in Android. While Jetpack Compose offers modern alternatives, proficiency in XML-based UI development remains crucial for many Android development tasks. This post provided a basic implementation. More sophisticated applications might incorporate background processing, data persistence, and visualizations of historical step data.