Utilisez le Sensor Manager pour remplir les données de pas dans une application mobile, comme décrit dans ce guide. Pour en savoir plus sur la conception et la gestion de l'UI d'une application d'exercice, consultez Créer une application de fitness de base.
Premiers pas
Pour commencer à mesurer les pas de votre compteur de pas de base depuis votre appareil mobile, vous devez ajouter les dépendances au fichier build.gradle
de votre module d'application. Vérifiez que vous utilisez les dernières versions des dépendances.
De plus, lorsque vous étendez la compatibilité de votre application à d'autres facteurs de forme, tels que Wear OS, ajoutez les dépendances requises par ces facteurs de forme.
Voici quelques exemples de dépendances de l'UI. Pour obtenir la liste complète, consultez le guide Éléments d'interface utilisateur.
implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation("androidx.activity:activity-compose")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material:material")
Obtenir le capteur de pas
Une fois que l'utilisateur a accordé l'autorisation de reconnaissance de l'activité nécessaire, vous pouvez accéder au capteur de compteur de pas :
- Obtenez l'objet
SensorManager
à partir degetSystemService()
. - Obtenez le capteur de compteur de pas à partir de
SensorManager
:
private val sensorManager by lazy {
getSystemService(Context.SENSOR_SERVICE) as SensorManager }
private val sensor: Sensor? by lazy {
sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) }
Certains appareils ne sont pas équipés du capteur de pas. Vous devez rechercher le capteur et afficher un message d'erreur si l'appareil n'en possède pas :
if (sensor == null) {
Text(text = "Step counter sensor is not present on this device")
}
Créer votre service de premier plan
Dans une application de fitness de base, vous pouvez avoir un bouton pour recevoir les événements de début et de fin de l'utilisateur afin de suivre les pas.
N'oubliez pas de suivre les bonnes pratiques concernant les capteurs. En particulier, le capteur de compteur de pas ne doit compter les pas que lorsque l'écouteur de capteur est enregistré. En associant l'enregistrement du capteur à un service de premier plan, le capteur est enregistré aussi longtemps que nécessaire et peut rester enregistré lorsque l'application n'est pas au premier plan.
Utilisez l'extrait de code suivant pour désenregistrer le capteur dans la méthode onPause()
de votre service de premier plan :
override fun onPause() {
super.onPause()
sensorManager.unregisterListener(this)
}
Analyser les données pour les événements
Pour accéder aux données du capteur, implémentez l'interface SensorEventListener
. Notez que vous devez associer l'enregistrement du capteur au cycle de vie de votre service de premier plan, en désenregistrant le capteur lorsque le service est mis en pause ou arrêté. L'extrait de code suivant montre comment implémenter l'interface SensorEventListener
pour Sensor.TYPE_STEP_COUNTER
:
private const val TAG = "STEP_COUNT_LISTENER"
context(Context)
class StepCounter {
private val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
suspend fun steps() = suspendCancellableCoroutine { continuation ->
Log.d(TAG, "Registering sensor listener... ")
val listener: SensorEventListener by lazy {
object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
if (event == null) return
val stepsSinceLastReboot = event.values[0].toLong()
Log.d(TAG, "Steps since last reboot: $stepsSinceLastReboot")
if (continuation.isActive) {
continuation.resume(stepsSinceLastReboot)
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
Log.d(TAG, "Accuracy changed to: $accuracy")
}
}
}
val supportedAndEnabled = sensorManager.registerListener(listener,
sensor, SensorManager.SENSOR_DELAY_UI)
Log.d(TAG, "Sensor listener registered: $supportedAndEnabled")
}
}
Créer une base de données pour les événements de capteur
Votre application peut afficher un écran sur lequel l'utilisateur peut consulter son nombre de pas au fil du temps. Pour offrir cette possibilité à votre application, utilisez la bibliothèque de persistance Room.
L'extrait suivant crée une table contenant un ensemble de mesures du nombre de pas, ainsi que l'heure à laquelle votre application a accédé à chaque mesure :
@Entity(tableName = "steps")
data class StepCount(
@ColumnInfo(name = "steps") val steps: Long,
@ColumnInfo(name = "created_at") val createdAt: String,
)
Créez un objet d'accès aux données (DAO) pour lire et écrire les données :
@Dao
interface StepsDao {
@Query("SELECT * FROM steps")
suspend fun getAll(): List<StepCount>
@Query("SELECT * FROM steps WHERE created_at >= date(:startDateTime) " +
"AND created_at < date(:startDateTime, '+1 day')")
suspend fun loadAllStepsFromToday(startDateTime: String): Array<StepCount>
@Insert
suspend fun insertAll(vararg steps: StepCount)
@Delete
suspend fun delete(steps: StepCount)
}
Pour instancier le DAO, créez un objet RoomDatabase
:
@Database(entities = [StepCount::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun stepsDao(): StepsDao
}
Stocker les données du capteur dans la base de données
ViewModel utilise la nouvelle classe StepCounter. Vous pouvez donc stocker les pas dès que vous les lisez :
viewModelScope.launch {
val stepsFromLastBoot = stepCounter.steps()
repository.storeSteps(stepsFromLastBoot)
}
La classe repository
se présenterait comme suit :
class Repository(
private val stepsDao: StepsDao,
) {
suspend fun storeSteps(stepsSinceLastReboot: Long) = withContext(Dispatchers.IO) {
val stepCount = StepCount(
steps = stepsSinceLastReboot,
createdAt = Instant.now().toString()
)
Log.d(TAG, "Storing steps: $stepCount")
stepsDao.insertAll(stepCount)
}
suspend fun loadTodaySteps(): Long = withContext(Dispatchers.IO) {
printTheWholeStepsTable() // DEBUG
val todayAtMidnight = (LocalDateTime.of(LocalDate.now(), LocalTime.MIDNIGHT).toString())
val todayDataPoints = stepsDao.loadAllStepsFromToday(startDateTime = todayAtMidnight)
when {
todayDataPoints.isEmpty() -> 0
else -> {
val firstDataPointOfTheDay = todayDataPoints.first()
val latestDataPointSoFar = todayDataPoints.last()
val todaySteps = latestDataPointSoFar.steps - firstDataPointOfTheDay.steps
Log.d(TAG, "Today Steps: $todaySteps")
todaySteps
}
}
}
}
Récupérer périodiquement les données des capteurs
Si vous utilisez un service de premier plan, vous n'avez pas besoin de configurer WorkManager
, car le nombre total de pas mis à jour devrait s'afficher dans votre application pendant la période où elle suit activement les pas de l'utilisateur.
Toutefois, si vous souhaitez regrouper vos enregistrements de pas, vous pouvez utiliser WorkManager
pour mesurer les pas à un intervalle spécifique, par exemple toutes les 15 minutes.
WorkManager
est le composant qui effectue le travail en arrière-plan pour une exécution fiable. Pour en savoir plus, consultez l'atelier de programmation WorkManager.
Pour configurer l'objet Worker
afin de récupérer les données, remplacez la méthode doWork()
, comme indiqué dans l'extrait de code suivant :
private const val TAG = " StepCounterWorker"
@HiltWorker
class StepCounterWorker @AssistedInject constructor(
@Assisted appContext: Context,
@Assisted workerParams: WorkerParameters,
val repository: Repository,
val stepCounter: StepCounter
) : CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
Log.d(TAG, "Starting worker...")
val stepsSinceLastReboot = stepCounter.steps().first()
if (stepsSinceLastReboot == 0L) return Result.success()
Log.d(TAG, "Received steps from step sensor: $stepsSinceLastReboot")
repository.storeSteps(stepsSinceLastReboot)
Log.d(TAG, "Stopping worker...")
return Result.success()
}
}
Pour configurer WorkManager
afin qu'il stocke le nombre de pas actuel toutes les 15 minutes, procédez comme suit :
- Étendez la classe
Application
pour implémenter l'interfaceConfiguration.Provider
. - Dans la méthode
onCreate()
, mettez en file d'attente unPeriodicWorkRequestBuilder
.
Ce processus est illustré dans l'extrait de code suivant :
@HiltAndroidApp
@RequiresApi(Build.VERSION_CODES.S)
internal class PulseApplication : Application(), Configuration.Provider {
@Inject
lateinit var workerFactory: HiltWorkerFactory
override fun onCreate() {
super.onCreate()
val myWork = PeriodicWorkRequestBuilder<StepCounterWorker>(
15, TimeUnit.MINUTES).build()
WorkManager.getInstance(this)
.enqueueUniquePeriodicWork("MyUniqueWorkName",
ExistingPeriodicWorkPolicy.UPDATE, myWork)
}
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(android.util.Log.DEBUG)
.build()
}
Pour initialiser le fournisseur de contenu qui contrôle l'accès à la base de données du compteur de pas de votre application immédiatement au démarrage de l'application, ajoutez l'élément suivant au fichier manifeste de votre application :
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />