Use o Gerenciador de sensores para preencher dados de passos em um app para dispositivos móveis, conforme descrito guia. Para mais informações sobre como projetar e gerenciar uma interface de app de exercícios, consulte Criar um app básico de condicionamento físico.
Primeiros passos
Para começar a medir as etapas do contador básico de passos do
dispositivo móvel, será necessário adicionar as dependências ao módulo do app
build.gradle
. Use as versões mais recentes das dependências.
Além disso, quando você estende a compatibilidade do app a outros formatos, como Wear OS,
adicione as dependências exigidas por esses formatos.
Confira abaixo alguns exemplos de algumas das dependências da interface. Para uma lista completa, consulte este guia de Elementos da interface.
implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation("androidx.activity:activity-compose")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material:material")
Acessar o sensor do contador de passos
Depois que o usuário conceder a permissão de reconhecimento de atividades necessária, acesse o sensor do contador de passos:
- Consiga o objeto
SensorManager
degetSystemService()
. - Acesse o sensor contador de passos do
SensorManager
:
private val sensorManager by lazy {
getSystemService(Context.SENSOR_SERVICE) as SensorManager }
private val sensor: Sensor? by lazy {
sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) }
Alguns dispositivos não têm esse sensor. Verifique se o sensor e mostrar uma mensagem de erro se o dispositivo não tiver uma:
if (sensor == null) {
Text(text = "Step counter sensor is not present on this device")
}
Criar um serviço em primeiro plano
Em um app fitness básico, você pode ter um botão para receber eventos de início e parada do usuário para rastrear as etapas.
Atenção às práticas recomendadas de sensores. Em particular, o sensor contador de passos conta apenas os passos, listener está registrado. Associando o registro do sensor a um primeiro plano serviço, o sensor fica registrado pelo tempo necessário e pode permanecem registrados quando o app não está em primeiro plano.
Use o snippet a seguir para cancelar o registro do sensor no método onPause()
de
seu serviço em primeiro plano:
override fun onPause() {
super.onPause()
sensorManager.unregisterListener(this)
}
Analisar dados de eventos
Para acessar os dados do sensor, implemente a interface SensorEventListener
. Observação
que você deve associar o registro do sensor aos arquivos
cancelar o registro do sensor quando o serviço for pausado ou encerrado. A
O snippet a seguir mostra como implementar a interface SensorEventListener
.
para 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")
}
}
Criar um banco de dados para os eventos do sensor
Seu app pode mostrar uma tela em que o usuário possa conferir os passos ao longo do tempo. Para oferecer esse recurso no seu app, use a biblioteca de persistência do Room.
O snippet a seguir cria uma tabela que contém um conjunto de contagem de passos medições, além do momento em que seu app acessou cada medição:
@Entity(tableName = "steps")
data class StepCount(
@ColumnInfo(name = "steps") val steps: Long,
@ColumnInfo(name = "created_at") val createdAt: String,
)
Criar um objeto de acesso a dados (DAO, na sigla em inglês) para ler e gravar os dados:
@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)
}
Para instanciar o DAO, crie um objeto RoomDatabase
:
@Database(entities = [StepCount::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun stepsDao(): StepsDao
}
Armazenar os dados do sensor no banco de dados
O ViewModel usa a nova classe StepCounter para que você possa armazenar as etapas assim que possível. enquanto os lê:
viewModelScope.launch {
val stepsFromLastBoot = stepCounter.steps()
repository.storeSteps(stepsFromLastBoot)
}
A classe repository
ficaria assim:
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
}
}
}
}
Recuperar periodicamente os dados do sensor
Se você usa um serviço em primeiro plano, não é necessário configurar o WorkManager
porque, durante o tempo em que seu aplicativo
monitora ativamente os passos do usuário,
a contagem total de passos atualizada será exibida no aplicativo.
No entanto, se você quiser agrupar os registros de passos em lote, use WorkManager
para
medir passos em um intervalo específico, como uma vez a cada 15 minutos.
WorkManager
é o componente que executa o segundo plano.
para execução garantida. Saiba mais no codelab do WorkManager.
Para configurar o objeto Worker
para extrair os dados, substitua doWork()
, conforme mostrado no snippet de código a seguir:
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()
}
}
Para configurar o WorkManager
para armazenar a contagem atual de passos a cada 15 minutos, faça o seguinte:
o seguinte:
- Estenda a classe
Application
para implementar oConfiguration.Provider
. interface gráfica do usuário. - No método
onCreate()
, coloque umPeriodicWorkRequestBuilder
na fila.
Esse processo aparece no snippet de código a seguir:
@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()
}
Inicializar o provedor de conteúdo que controla o acesso à etapa do seu app do banco de dados de contadores imediatamente após a inicialização do app, adicione o seguinte elemento ao arquivo de manifesto do seu app:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />