π§ What We’ll Build
- Detects and displays your steps using the step detector sensor.
- Reactively updates the UI with the current step count.
- Handles activity recognition permission.
- Follows best practices with ViewModel and state handling in Compose.
π§° Tools & Tech Stack
- Jetpack Compose – UI toolkit
- ViewModel – Logic holder across configuration changes
- SensorManager – Access to step detector
- Wear OS Emulator or Physical Watch – To test
- Kotlin – Our primary programming language
π Step 1: Create a New Wear OS Project
- Make sure to select:
- Kotlin
- Minimum SDK: API 26 (Android 8.0 Oreo) or higher
- Name your app something like StepTrackerWear
π¦ Step 2: Add Required Dependencies
// ViewModel for Compose
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.9.0")
// ViewModel for Android application (used with Application context)
implementation("androidx.lifecycle:lifecycle-viewmodel:2.9.0")
// If you use LiveData or lifecycle-aware components (optional)
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.9.0")
π§ Step 3: Create the ViewModel for Step Tracking
class StepCounterViewModel(application: Application) : AndroidViewModel(application), SensorEventListener {
private val sensorManager = application.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val stepSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
private val _steps = mutableIntStateOf(0)
val steps: IntState get() = _steps // ✅ Correct
private var initialSteps: Float = -1f
fun startTracking() {
stepSensor?.let {
sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_UI)
}
}
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
if (initialSteps < 0) {
initialSteps = it.values[0]
}
_steps.intValue = (it.values[0] - initialSteps).toInt()
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
}
π¨ Step 4: Create the UI Screen with Compose
@Composable
fun StepCounterScreen(viewModel: StepCounterViewModel) {
val steps by viewModel.steps
val stepGoal = 5000
val progress = steps / stepGoal.toFloat()
Scaffold(
timeText = { TimeText() }
) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Spacer(Modifier.height(18.dp))
Text("Steps", style = MaterialTheme.typography.title3)
Text("$steps", style = MaterialTheme.typography.display1)
Text("www.codingbihar.com")
CircularProgressIndicator(
progress = progress.coerceAtMost(1f),
modifier = Modifier
.padding(16.dp)
.size(100.dp),
strokeWidth = 8.dp
)
Text("Goal: $stepGoal", style = MaterialTheme.typography.title3)
}
}
}
}
π Step 5: Request ACTIVITY_RECOGNITION Permission
Now in your MainActivity, request it if not granted:<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
private val PERMISSION_REQUEST_CODE = 1001
π Step 6: Finalize the MainActivity
class MainActivity : ComponentActivity() {
private lateinit var viewModel: StepCounterViewModel
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory.getInstance(application)
)[StepCounterViewModel::class.java]
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACTIVITY_RECOGNITION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.ACTIVITY_RECOGNITION),
1001
)
}
viewModel.startTracking()
setTheme(android.R.style.Theme_DeviceDefault)
setContent {
StepCounterScreen(viewModel)
}
}
}
Output:
π― Optional Enhancements
π Add a Goal Tracker
π§ Add Persistent History
π Celebrate Milestones
π± Testing Tips
Extended Controls > Sensors > Virtual Sensors > Step Counter



