किसी मोबाइल डिवाइस से कदमों की संख्या मापने के लिए, सेंसर मैनेजर का इस्तेमाल करें

इस गाइड में बताए गए तरीके से, किसी मोबाइल ऐप्लिकेशन में कदमों का डेटा भरने के लिए, Sensor Manager का इस्तेमाल करें. एक्सरसाइज़ ऐप्लिकेशन के यूज़र इंटरफ़ेस (यूआई) को डिज़ाइन और मैनेज करने के बारे में ज़्यादा जानने के लिए, बुनियादी फ़िटनेस ऐप्लिकेशन बनाना लेख पढ़ें.

शुरू करना

अपने मोबाइल डिवाइस पर, बुनियादी स्टेप काउंटर से कदमों की संख्या मेज़र करने के लिए, आपको अपने ऐप्लिकेशन मॉड्यूल की build.gradle फ़ाइल में डिपेंडेंसी जोड़नी होंगी. पुष्टि करें कि आपने डिपेंडेंसी के नए वर्शन इस्तेमाल किए हों. इसके अलावा, जब अपने ऐप्लिकेशन को अन्य डिवाइसों के नाप या आकार के लिए उपलब्ध कराया जाता है, जैसे कि Wear OS, तो उन डिपेंडेंसी को जोड़ें जिनकी ज़रूरत इन डिवाइसों के नाप या आकार के लिए होती है.

यूज़र इंटरफ़ेस (यूआई) की कुछ डिपेंडेंसी के उदाहरण यहां दिए गए हैं. पूरी सूची देखने के लिए, यूज़र इंटरफ़ेस (यूआई) एलिमेंट गाइड देखें.

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

स्टेप काउंटर सेंसर पाना

उपयोगकर्ता से गतिविधि पहचानने की ज़रूरी अनुमति मिलने के बाद, आपके पास स्टेप काउंटर सेंसर को ऐक्सेस करने का विकल्प होता है:

  1. getSystemService() से SensorManager ऑब्जेक्ट पाएं.
  2. 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 इंटरफ़ेस लागू करें. ध्यान दें कि आपको सेंसर रजिस्ट्रेशन को अपनी फ़ोरग्राउंड सेवा के लाइफ़साइकल से जोड़ना चाहिए. साथ ही, सेवा के रुकने या बंद होने पर सेंसर का रजिस्ट्रेशन रद्द कर देना चाहिए. नीचे दिए गए स्निपेट में, Sensor.TYPE_STEP_COUNTER के लिए SensorEventListener इंटरफ़ेस को लागू करने का तरीका बताया गया है:

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
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 कोडलैब में इसके बारे में ज़्यादा जानें.

डेटा वापस पाने के लिए, 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 को सेट अप करने के लिए यह तरीका अपनाएं:

  1. Application इंटरफ़ेस को लागू करने के लिए, Application क्लास को बढ़ाएं.Configuration.Provider
  2. 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" />