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

Utilisez Sensor Manager pour renseigner les données de pas dans une application mobile, comme décrit dans ce . Pour en savoir plus sur la conception et la gestion d'une UI d'application d'exercice, se référer à Créer une application de fitness basique

Premiers pas

Pour commencer à mesurer les pas de votre compteur de pas de base à partir de votre appareil mobile, vous devez ajouter les dépendances au module de votre application build.gradle. Assurez-vous d'utiliser les dernières versions des dépendances. De plus, lorsque vous étendez la prise en charge de votre application à d'autres facteurs de forme, comme Wear OS, ajouter les dépendances requises par ces facteurs de forme.

Vous trouverez ci-dessous quelques exemples de dépendances de l'interface utilisateur. Pour obtenir une 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")

Munissez-vous du 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 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 du compteur de pas. Vous devez vérifier que le capteur et afficher un message d'erreur si l'appareil n'en est pas équipé:

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émarrage et d'arrêt de l'utilisateur pour suivre les étapes.

Respectez les bonnes pratiques concernant les capteurs. En particulier, le capteur du compteur de pas ne doit compter que les pas "listener" est enregistré. En associant l'enregistrement des capteurs à un premier plan le capteur est enregistré tant que nécessaire et peut restent enregistrées lorsque l'application n'est pas exécutée 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. Remarque associer l'enregistrement des capteurs au réseau en annulant l'enregistrement du capteur à l'arrêt ou à la mise en pause du service. La 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 des capteurs

Votre application peut afficher un écran permettant à l'utilisateur de voir ses pas au fil du temps. Pour fournir cette fonctionnalité dans votre application, utilisez la bibliothèque de persistance Room.

L'extrait de code suivant crée un tableau contenant un ensemble de pas mesures, 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éer 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 pour que vous puissiez stocker les étapes dès que possible lorsque 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 En effet, lorsque votre application suit activement les pas de l'utilisateur, le nombre total de pas mis à jour devrait apparaître dans votre application.

Toutefois, si vous souhaitez regrouper vos enregistrements de pas, vous pouvez utiliser WorkManager pour mesurez les pas à un intervalle spécifique, par exemple une fois toutes les 15 minutes. WorkManager est le composant qui effectue l'arrière-plan. pour garantir une exécution garantie. Pour en savoir plus, consultez l'atelier de programmation WorkManager.

Pour configurer l'objet Worker afin de récupérer les données, remplacez 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, les éléments suivants:

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

Ce processus est indiqué 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 à l'étape de votre application de compteur de données immédiatement après le démarrage de l'application, ajoutez l'élément suivant à le fichier manifeste de votre application:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    tools:node="remove" />