Utiliser le Gestionnaire de capteurs pour mesurer les pas à partir d'un appareil mobile

Utilisez le gestionnaire de capteurs pour renseigner les données sur les pas dans une application mobile, comme décrit dans ce guide. Pour en savoir plus sur la conception et la gestion de l'interface utilisateur d'une application d'exercice, consultez Créer une application de fitness de base.

Lancez-vous

Pour commencer à mesurer les étapes du compteur de pas de base à partir de votre appareil mobile, vous devez ajouter les dépendances au fichier build.gradle de votre module d'application. Assurez-vous d'utiliser 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.

Vous trouverez ci-dessous quelques exemples de dépendances de l'UI. Pour obtenir la liste complète, consultez ce guide sur les é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 du compteur de pas

Une fois que l'utilisateur a accordé l'autorisation de reconnaissance d'activité nécessaire, vous pouvez accéder au capteur du compteur de pas:

  1. Obtenez l'objet SensorManager à partir de getSystemService().
  2. Procurez-vous le capteur du compteur de pas à partir du 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 compteur de pas. Vous devez le vérifier 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 disposer d'un bouton permettant de recevoir des événements de démarrage et d'arrêt de l'utilisateur pour le suivi des pas.

Tenez compte des bonnes pratiques concernant les capteurs. En particulier, le capteur du compteur de pas ne doit compter que les pas tant que l'écouteur du capteur est enregistré. Lorsque vous associez l'enregistrement des capteurs à un service de premier plan, le capteur est enregistré tant que cela est nécessaire, et il peut le rester lorsque l'application n'est pas au premier plan.

Utilisez l'extrait de code suivant pour annuler l'enregistrement du capteur dans la méthode onPause() de votre service de premier plan:

override fun onPause() {
    super.onPause()
    sensorManager.unregisterListener(this)
}

Analyser les données d'événements

Pour accéder aux données des capteurs, implémentez l'interface SensorEventListener. Notez que vous devez associer l'enregistrement des capteurs au cycle de vie de votre service de premier plan, en annulant l'enregistrement du capteur lorsque le service est suspendu ou terminé. 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 capteurs

Votre application peut afficher un écran sur lequel l'utilisateur peut consulter ses pas au fil du temps. Pour proposer cette fonctionnalité dans votre application, utilisez la bibliothèque de persistance Room.

L'extrait de code 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 des capteurs dans la base de données

ViewModel utilise la nouvelle classe StepCounter, ce qui vous permet de stocker les étapes dès que vous les lisez:

viewModelScope.launch {
    val stepsFromLastBoot = stepCounter.steps()
    repository.storeSteps(stepsFromLastBoot)
}

La classe repository se présente 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ération périodique des données des capteurs

Si vous utilisez un service de premier plan, vous n'avez pas besoin de configurer WorkManager, car pendant que votre application suit activement les pas de l'utilisateur, le nombre total de pas mis à jour doit s'afficher dans votre application.

Toutefois, si vous souhaitez regrouper vos enregistrements de pas, vous pouvez utiliser WorkManager pour mesurer les pas à un intervalle spécifique, par exemple une fois toutes les 15 minutes. WorkManager est le composant qui effectue le travail en arrière-plan pour une exécution garantie. Pour en savoir plus, consultez l'atelier de programmation sur 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 de stocker le nombre de pas actuel toutes les 15 minutes, procédez comme suit:

  1. Étendez la classe Application pour implémenter l'interface Configuration.Provider.
  2. Dans la méthode onCreate(), mettez un PeriodicWorkRequestBuilder en file d'attente.

Ce processus apparaît 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 de compteur de pas de votre application immédiatement après son démarrage, 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" />