Menggunakan Pengelola Sensor untuk mengukur langkah dari perangkat seluler

Gunakan Pengelola Sensor untuk mengisi data langkah di aplikasi seluler, seperti yang dijelaskan dalam panduan ini. Untuk mengetahui informasi selengkapnya tentang cara mendesain dan mengelola UI aplikasi latihan fisik, lihat Membangun aplikasi kebugaran dasar.

Memulai

Untuk mulai mengukur langkah-langkah penghitung langkah dasar dari perangkat seluler, Anda harus menambahkan dependensi ke file build.gradle modul aplikasi. Pastikan Anda menggunakan versi terbaru dependensi. Selain itu, saat Anda memperluas dukungan aplikasi ke faktor bentuk lain, seperti Wear OS, tambahkan dependensi yang diperlukan oleh faktor bentuk ini.

Berikut beberapa contoh beberapa dependensi UI. Untuk daftar lengkapnya, lihat panduan Elemen UI ini.

implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation("androidx.activity:activity-compose")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material:material")

Mendapatkan sensor penghitung langkah

Setelah pengguna memberikan izin pengenalan aktivitas yang diperlukan, Anda dapat mengakses sensor penghitung langkah:

  1. Dapatkan objek SensorManager dari getSystemService().
  2. Dapatkan sensor penghitung langkah dari SensorManager:
private val sensorManager by lazy {
        getSystemService(Context.SENSOR_SERVICE) as SensorManager }
private val sensor: Sensor? by lazy {
        sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) }

Beberapa perangkat tidak memiliki sensor penghitung langkah. Anda harus memeriksa sensor dan menampilkan pesan error jika perangkat tidak memilikinya:

if (sensor == null) {
    Text(text = "Step counter sensor is not present on this device")
}

Membuat layanan latar depan

Di aplikasi kebugaran dasar, Anda mungkin memiliki tombol untuk menerima peristiwa mulai dan berhenti dari pengguna untuk melacak langkah.

Perhatikan praktik terbaik sensor. Khususnya, sensor penghitung langkah hanya boleh menghitung langkah saat pemroses sensor terdaftar. Dengan mengaitkan pendaftaran sensor dengan layanan latar depan, sensor akan didaftarkan selama diperlukan, dan sensor dapat tetap didaftarkan saat aplikasi tidak berada di latar depan.

Gunakan cuplikan berikut untuk membatalkan pendaftaran sensor dalam metode onPause() layanan latar depan Anda:

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

Menganalisis data untuk peristiwa

Untuk mengakses data sensor, terapkan antarmuka SensorEventListener. Perhatikan bahwa Anda harus mengaitkan pendaftaran sensor dengan siklus proses layanan latar depan, membatalkan pendaftaran sensor saat layanan dijeda atau diakhiri. Cuplikan berikut menunjukkan cara mengimplementasikan antarmuka SensorEventListener untuk 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")
    }
}

Buat database untuk peristiwa sensor

Aplikasi Anda mungkin menampilkan layar tempat pengguna dapat melihat langkah-langkah mereka dari waktu ke waktu. Untuk menyediakan kemampuan ini di aplikasi Anda, gunakan library persistensi Room.

Cuplikan berikut membuat tabel yang berisi serangkaian pengukuran jumlah langkah, beserta waktu saat aplikasi Anda mengakses setiap pengukuran:

@Entity(tableName = "steps")
data class StepCount(
  @ColumnInfo(name = "steps") val steps: Long,
  @ColumnInfo(name = "created_at") val createdAt: String,
)

Buat objek akses data (DAO) untuk membaca dan menulis data:

@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)
}

Untuk membuat instance DAO, buat objek RoomDatabase:

@Database(entities = [StepCount::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun stepsDao(): StepsDao
}

Menyimpan data sensor ke dalam database

ViewModel menggunakan class StepCounter baru, sehingga Anda dapat menyimpan langkah-langkah segera setelah Anda membacanya:

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

Class repository akan terlihat seperti ini:

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
            }
        }
    }
}


Mengambil data sensor secara berkala

Jika Anda menggunakan layanan latar depan, Anda tidak perlu mengonfigurasi WorkManager karena, selama waktu saat aplikasi Anda secara aktif melacak langkah pengguna, jumlah langkah total yang diperbarui akan muncul di aplikasi Anda.

Namun, jika Anda ingin mengelompokkan data langkah, Anda dapat menggunakan WorkManager untuk mengukur langkah pada interval tertentu, seperti sekali setiap 15 menit. WorkManager adalah komponen yang melakukan pekerjaan di latar belakang untuk eksekusi yang andal. Pelajari lebih lanjut di codelab WorkManager.

Untuk mengonfigurasi objek Worker guna mengambil data, ganti metode doWork(), seperti yang ditunjukkan dalam cuplikan kode berikut:

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()
    }
}

Untuk menyiapkan WorkManager guna menyimpan jumlah langkah saat ini setiap 15 menit, lakukan hal berikut:

  1. Perluas class Application untuk mengimplementasikan antarmuka Configuration.Provider.
  2. Dalam metode onCreate(), antrekan PeriodicWorkRequestBuilder.

Proses ini muncul dalam cuplikan kode berikut:

@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()
}

Untuk menginisialisasi penyedia konten yang mengontrol akses ke database penghitung langkah aplikasi Anda segera setelah startup aplikasi, tambahkan elemen berikut ke file manifes aplikasi Anda:

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