Utilizza Sensor Manager per compilare i dati sui passaggi in un'app mobile, come descritto in questa guida. Per scoprire di più su come progettare e gestire la UI di un'app per l'allenamento, consulta l'articolo Creare un'app per il fitness di base.
Per iniziare
Per iniziare a misurare i passi del contapassi di base dal
dispositivo mobile, devi aggiungere le dipendenze al file build.gradle
del modulo dell'app. Assicurati di utilizzare le versioni più recenti delle dipendenze.
Inoltre, quando estendi il supporto della tua app ad altri fattori di forma, come Wear OS, aggiungi le dipendenze richieste da questi fattori.
Di seguito sono riportati alcuni esempi di alcune dipendenze UI. Per un elenco completo, consulta questa guida sugli elementi UI.
implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation("androidx.activity:activity-compose")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material:material")
Ottenere il sensore del contapassi
Dopo che l'utente ha concesso la necessaria autorizzazione di riconoscimento attività, puoi accedere al sensore del contapassi:
- Ottieni l'oggetto
SensorManager
dagetSystemService()
. - Acquisisci il sensore contapassi da
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. Controlla il sensore e mostra un messaggio di errore se il dispositivo non ne ha uno:
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, potresti avere un pulsante per ricevere gli eventi di inizio e di fine dell'utente per il monitoraggio dei passi.
Tieni presente le best practice relative ai sensori. In particolare, il sensore del contapassi deve contare i passi solo mentre l'ascoltatore del sensore è registrato. Se associ la registrazione del sensore a un servizio in primo piano, il sensore viene registrato per tutto il tempo necessario e può rimanere registrato quando l'app non è in primo piano.
Utilizza il seguente snippet per annullare la registrazione del sensore nel metodo onPause()
del
servizio in primo piano:
override fun onPause() {
super.onPause()
sensorManager.unregisterListener(this)
}
Analizzare i dati degli eventi
Per accedere ai dati del sensore, implementa l'interfaccia SensorEventListener
. Tieni presente che devi associare la registrazione del sensore al ciclo di vita del servizio in primo piano, annullando la registrazione del sensore quando il servizio è in pausa o terminato. 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ò vedere i propri passi nel tempo. Per fornire questa funzionalità nella tua app, utilizza la libreria della persistenza della stanza.
Lo snippet seguente crea una tabella contenente un insieme di misurazioni del numero di passi, insieme all'ora in cui la tua app ha eseguito l'accesso a ciascuna misurazione:
@Entity(tableName = "steps")
data class StepCount(
@ColumnInfo(name = "steps") val steps: Long,
@ColumnInfo(name = "created_at") val createdAt: String,
)
Crea 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 del DAO, crea un oggetto RoomDatabase
:
@Database(entities = [StepCount::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun stepsDao(): StepsDao
}
Archiviare i dati del sensore nel database
ViewModel utilizza la nuova classe StepCounter, pertanto puoi memorizzare i passaggi non appena li leggi:
viewModelScope.launch {
val stepsFromLastBoot = stepCounter.steps()
repository.storeSteps(stepsFromLastBoot)
}
Il corso repository
avrà il seguente aspetto:
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
}
}
}
}
Recuperare periodicamente i dati dei sensori
Se utilizzi un servizio in primo piano, non è necessario configurare WorkManager
perché, quando l'app monitora attivamente i passi dell'utente, il conteggio dei passi totale aggiornato dovrebbe essere visualizzato nell'app.
Tuttavia, se vuoi raggruppare i record dei passi, puoi utilizzare WorkManager
per misurare i passaggi a intervalli specifici, ad esempio una volta ogni 15 minuti.
WorkManager
è il componente che esegue le operazioni in background
per garantire l'esecuzione. Scopri di più nel codelab di WorkManager.
Per configurare l'oggetto Worker
al fine di recuperare i dati, sostituisci il metodo doWork()
, come mostrato nel seguente snippet di codice:
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 che archivi il conteggio dei passi corrente ogni 15 minuti:
- Estendi la classe
Application
per implementare l'interfacciaConfiguration.Provider
. - 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 database del contatore di passaggi della tua app subito all'avvio, aggiungi il seguente elemento al file manifest dell'app:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />