Build a basic fitness app

This guide walks you through building a basic mobile step counter app, which is a common foundation for many Health & Fitness apps.

This workflow integrates the following APIs:

  • SensorManager for retrieving steps data from a mobile device.
  • Room for local data storage.
  • Health Connect for storing and sharing health and fitness data on device.

For additional support on data reading and the tools necessary, refer to Use Android Sensor Manager to track steps from a mobile device.

If you haven't already set up your development environment for using Health Connect, follow these getting started steps.

Request permissions on handheld device

Before getting exercise data you must request and be granted the appropriate permissions.

As a best practice, only request the permissions you need, and make sure to request each permission in context, instead of requesting all permissions at once when the user starts the app.

The step counter sensor, which many exercise apps rely on, uses the ACTIVITY_RECOGNITION permission. Add this permission in your AndroidManifest.xml file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools">

  <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>

</manifest>

To request the ACTIVITY_RECOGNITION permission at runtime refer to the permission request documentation.

Manage UI state using a ViewModel

To properly manage the UI state, use a ViewModel. Jetpack Compose and ViewModels offers you a more in-depth look at this workflow.

Also, use UI layering, which is a critical part for building UIs with Compose and lets you follow architecture best practices, such as Unidirectional Data Flow. To learn more about UI layering, refer to the UI layer documentation.

In this example app, the UI has three basic states:

  • Loading: Shows a spinning circle.
  • Content: Shows information about your steps for today.
  • Error: Shows a message when something goes wrong.

The ViewModel exposes these states as a Kotlin Flow. Use a sealed class to contain the classes and objects that represent the possible states:

class TodayScreenViewModel(...) {

  val currentScreenState: MutableStateFlow<TodayScreenState> = MutableStateFlow(Loading)

  [...]

}

sealed class TodayScreenState {
    data object Loading : TodayScreenState()
    data class Content(val steps: Long, val dailyGoal: Long) : TodayScreenState()
    data object Error: TodayScreenState()
}

Compose UI then collects this Flow as a Compose State and acts on it:

val state: TodayScreenState = todayScreenViewModel.currentScreenState.collectAsState().value