モバイルアプリに歩数データを表示するには、こちら ご覧くださいエクササイズ アプリの UI の設計と管理方法について詳しくは、 参照 基本的なフィットネス アプリを作成します。
はじめに
お使いのスマートウオッチで基本的な歩数計の歩数測定を開始するには、
依存関係をアプリ モジュールに追加する必要があります。
build.gradle
ファイル。最新バージョンの依存関係を使用していることを確認します。
また、アプリのサポートを Wear OS などの他のフォーム ファクタに拡張すると、
これらのフォーム ファクタに必要な依存関係を追加します。
UI の依存関係の例を以下に示します。完全なリストについては 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")
歩数計センサーを取得する
必要な操作の認識の権限をユーザーが付与した後、 歩数計センサーにアクセスできます。
getSystemService()
からSensorManager
オブジェクトを取得します。SensorManager
から歩数計センサーを取得します。
private val sensorManager by lazy {
getSystemService(Context.SENSOR_SERVICE) as SensorManager }
private val sensor: Sensor? by lazy {
sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) }
デバイスによっては、歩数計センサーが搭載されていません。センサーを確認してください デバイスにこのモードがない場合はエラー メッセージが表示されます。
if (sensor == null) {
Text(text = "Step counter sensor is not present on this device")
}
フォアグラウンド サービスを作成する
基本的なフィットネス アプリでは、ボタン ユーザーが開始イベントと停止イベントを受け取り、ステップを追跡できるようにします。
センサーのベスト プラクティスを念頭に置いてください。 具体的には歩数計センサーは歩数をカウントし、センサーが 登録されます。センサー登録をフォアグラウンドに関連付ける サービスで必要な期間だけセンサーが登録され、 アプリがフォアグラウンドでなくても、登録されたままになります。
次のスニペットを使用して、onPause()
メソッドでセンサーの登録を解除します。
使用します。
override fun onPause() {
super.onPause()
sensorManager.unregisterListener(this)
}
イベントのデータを分析する
センサーデータにアクセスするには、SensorEventListener
インターフェースを実装します。備考
フォアグラウンド サービスの
センサーの登録を解除して、サービスの一時停止時または終了時にセンサーの登録を解除します。「
次のスニペットは、SensorEventListener
インターフェースを実装する方法を示しています。
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")
}
}
センサー イベントのデータベースを作成する
アプリに、歩数の推移を確認できる画面が表示される場合があります。 この機能をアプリで提供するには、Room 永続ライブラリを使用します。
次のスニペットは、歩数のセットを含むテーブルを作成します。 アプリが各測定値にアクセスした時刻とともに表示されます。
@Entity(tableName = "steps")
data class StepCount(
@ColumnInfo(name = "steps") val steps: Long,
@ColumnInfo(name = "created_at") val createdAt: String,
)
データアクセス オブジェクト(DAO)を作成する データを読み書きします。
@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)
}
DAO をインスタンス化するには、RoomDatabase
オブジェクトを作成します。
@Database(entities = [StepCount::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun stepsDao(): StepsDao
}
センサーデータをデータベースに保存する
ViewModel は新しい StepCounter クラスを使用するため、ステップをすぐに保存できます 説明します。
viewModelScope.launch {
val stepsFromLastBoot = stepCounter.steps()
repository.storeSteps(stepsFromLastBoot)
}
repository
クラスは次のようになります。
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
}
}
}
}
センサーデータを定期的に取得する
フォアグラウンド サービスを使用する場合、WorkManager
を構成する必要はありません
なぜなら、アプリがユーザーの歩数をアクティブに追跡している間は、
更新された合計歩数がアプリに表示されます。
ただし、歩数レコードをバッチ処理する場合は、WorkManager
を使用して以下を行うことができます。
15 分ごとなど、特定の間隔で歩数を測定します。
WorkManager
は、バックグラウンドを実行するコンポーネントです。
確実に実行されます詳しくは、WorkManager Codelab をご覧ください。
データを取得するように Worker
オブジェクトを構成するには、doWork()
をオーバーライドします。
メソッドを呼び出します。次のコード スニペットをご覧ください。
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()
}
}
現在の歩数を 15 分ごとに保存するように WorkManager
を設定するには、次のようにします。
次のとおりです。
Application
クラスを拡張してConfiguration.Provider
を実装する 行うことができます。onCreate()
メソッドで、PeriodicWorkRequestBuilder
をキューに追加します。
このプロセスを次のコード スニペットに示します。
@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()
}
アプリのステップへのアクセスを制御するコンテンツ プロバイダを初期化する アプリの起動時に直ちにカウンタ データベースを作成する場合は、 次のように指定します。
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />