Utilizza Gestione sensori per compilare i dati dei passi in un'app mobile, come descritto in guida. Per ulteriori informazioni su come progettare e gestire l'UI di un'app per l'allenamento, consulta le Crea un'app per l'attività fisica di base.
Per iniziare
Per iniziare a misurare i passi del contapassi di base dal tuo
dispositivo mobile, dovrai aggiungere le dipendenze al modulo dell'app
build.gradle
. Assicurati di utilizzare le versioni più recenti delle dipendenze.
Inoltre, quando estendi il supporto della tua app ad altri fattori di forma, ad esempio Wear OS,
aggiungere le dipendenze necessarie da questi fattori di forma.
Di seguito sono riportati alcuni esempi di alcune dipendenze della UI. Per un elenco completo, consulta questa guida agli elementi dell'interfaccia utente.
implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation("androidx.activity:activity-compose")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material:material")
Procurati il sensore contapassi
Dopo che l'utente ha concesso l'autorizzazione di riconoscimento attività necessaria, puoi accedere al sensore contapassi:
- Recupera l'oggetto
SensorManager
dagetSystemService()
. - Acquisisci il sensore del contapassi dal dispositivo
SensorManager
:
private val sensorManager by lazy {
getSystemService(Context.SENSOR_SERVICE) as SensorManager }
private val sensor: Sensor? by lazy {
sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) }
Alcuni dispositivi non sono dotati del sensore contapassi. Devi controllare la presenza del sensore e verrà mostrato un messaggio di errore se il dispositivo non dispone di un dispositivo:
if (sensor == null) {
Text(text = "Step counter sensor is not present on this device")
}
Crea il servizio in primo piano
In un'app per l'attività fisica di base, potrebbe essere presente un pulsante per ricevere eventi di avvio e interruzione dall'utente per il monitoraggio dei passi.
Ricorda le best practice relative ai sensori. In particolare, il sensore del contapassi deve contare i passi solo, mentre il sensore listener è registrato. Associando la registrazione del sensore a un primo piano assistenza, il sensore viene registrato per tutto il tempo necessario e può rimangono registrati quando l'app non è in primo piano.
Utilizza il seguente snippet per annullare la registrazione del sensore nel metodo onPause()
di
per il servizio in primo piano:
override fun onPause() {
super.onPause()
sensorManager.unregisterListener(this)
}
Analizzare i dati per gli eventi
Per accedere ai dati dei sensori, implementa l'interfaccia SensorEventListener
. Nota
è necessario associare la registrazione dei sensori al servizio
il ciclo di vita, annullando la registrazione del sensore quando il servizio viene messo in pausa o terminato. La
il seguente snippet mostra come implementare l'interfaccia SensorEventListener
per 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")
}
}
Crea un database per gli eventi dei sensori
L'app potrebbe mostrare una schermata in cui l'utente può visualizzare i passi effettuati nel tempo. Per fornire questa funzionalità nella tua app, utilizza la libreria di persistenza della stanza.
Lo snippet seguente crea una tabella contenente un insieme di conteggi dei passi e l'ora in cui l'app ha eseguito l'accesso a ogni misurazione:
@Entity(tableName = "steps")
data class StepCount(
@ColumnInfo(name = "steps") val steps: Long,
@ColumnInfo(name = "created_at") val createdAt: String,
)
Creare un oggetto di accesso ai dati (DAO) per leggere e scrivere i dati:
@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)
}
Per creare un'istanza DAO, crea un oggetto RoomDatabase
:
@Database(entities = [StepCount::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun stepsDao(): StepsDao
}
Archivia i dati dei sensori nel database
ViewModel utilizza la nuova classe StepCounter, quindi puoi memorizzare i passaggi non appena man mano che li leggi:
viewModelScope.launch {
val stepsFromLastBoot = stepCounter.steps()
repository.storeSteps(stepsFromLastBoot)
}
Il corso repository
sarà simile a questo:
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
}
}
}
}
Recupero periodico dei dati dei sensori
Se utilizzi un servizio in primo piano, non è necessario configurare WorkManager
perché, mentre l'app monitora attivamente i passi dell'utente,
dovrebbe apparire nella tua app il numero di passi totale aggiornato.
Se vuoi raggruppare i record dei passi, puoi utilizzare WorkManager
per
misurare i passi a un intervallo specifico, ad esempio una volta ogni 15 minuti.
WorkManager
è il componente che esegue lo sfondo
per un'esecuzione garantita. Scopri di più nel codelab di WorkManager.
Per configurare l'oggetto Worker
in modo da recuperare i dati, sostituisci doWork()
come mostrato nello snippet di codice riportato di seguito:
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()
}
}
Per configurare WorkManager
in modo da memorizzare il conteggio di passi attuale ogni 15 minuti, procedi nel seguente modo:
le seguenti:
- Estendi la classe
Application
per implementareConfiguration.Provider
a riga di comando. - Nel metodo
onCreate()
, accoda unPeriodicWorkRequestBuilder
.
Questa procedura viene visualizzata nel seguente snippet di codice:
@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()
}
Per inizializzare il fornitore di contenuti che controlla l'accesso al passaggio della tua app immediatamente all'avvio dell'app, aggiungi il seguente elemento a il file manifest dell'app:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />