אפשר להשתמש במנהל החיישנים כדי לאכלס את נתוני השלבים באפליקציה לנייד, כפי שמתואר כאן מותאמת אישית. לקבלת מידע נוסף על עיצוב וניהול ממשק משתמש של אפליקציית כושר, להתייחס אל לפתח אפליקציית כושר בסיסית.
תחילת העבודה
כדי להתחיל למדוד את השלבים של מונה הצעדים הבסיסי
בנייד, צריך להוסיף את יחסי התלות למודול האפליקציה
קובץ build.gradle
. חשוב להשתמש בגרסאות העדכניות של יחסי התלות.
בנוסף, כשמרחיבים את התמיכה של האפליקציה לגורמי צורה אחרים, כמו Wear OS,
מוסיפים את יחסי התלות שנדרשים לגורמי הצורה האלה.
בהמשך מוצגות כמה דוגמאות ליחסי התלות של ממשק המשתמש. לרשימה מלאה, לעיין במדריך הזה בנושא רכיבי 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")
משיגים את החיישן של מונה הצעדים
אחרי שהמשתמש העניק את ההרשאה הנדרשת לזיהוי פעילות, ניתן לגשת לחיישן מונה הצעדים:
- משיגים את האובייקט
SensorManager
מ-getSystemService()
. - צריך להשיג את החיישן של מונה הצעדים מה-
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")
}
יצירת שירות שפועל בחזית
באפליקציות כושר בסיסיות עשויות לכלול לחצן כדי לקבל מהמשתמש אירועי התחלה ועצירה לגבי שלבי המעקב.
חשוב לזכור את השיטות המומלצות לגבי חיישנים. באופן ספציפי, החיישן של מונה הצעדים אמור לספור צעדים רק בזמן שהחיישן ה-listener רשום. על ידי שיוך של רישום חיישן לחזית החיישן רשום כל עוד יש בו צורך, נשארים רשומים כשהאפליקציה לא בחזית
צריך להשתמש בקטע הקוד הבא כדי לבטל את רישום החיישן בשיטה 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")
}
}
יצירת מסד נתונים לאירועים מהחיישנים
ייתכן שבאפליקציה יוצג מסך שבו המשתמש יכול לראות את השלבים לאורך זמן. כדי לספק את היכולת הזו באפליקציה, צריך להשתמש בספרייה הקבועה של חדרים.
קטע הקוד הבא יוצר טבלה שמכילה קבוצה של ספירת צעדים המדידות, וגם הזמן שבו האפליקציה ניגשה לכל מדידה:
@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
הוא הרכיב שמבצע את הרקע
לביצוע מובטח. מידע נוסף זמין בקוד Lab של WorkManager.
כדי להגדיר את האובייקט 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()
}
}
כדי להגדיר את WorkManager
כך שיאחסן את ספירת הצעדים הנוכחית כל 15 דקות, צריך לבצע
הבאים:
- צריך להרחיב את המחלקה
Application
כדי להטמיע אתConfiguration.Provider
גרפי. - באמצעות method
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" />