একটি মোবাইল ডিভাইস থেকে পদক্ষেপ পরিমাপ করতে সেন্সর ম্যানেজার ব্যবহার করুন

এই নির্দেশিকায় বর্ণিত একটি মোবাইল অ্যাপে ধাপের ডেটা জমা করতে সেন্সর ম্যানেজার ব্যবহার করুন। একটি ব্যায়াম অ্যাপ 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")

ধাপ পাল্টা সেন্সর প্রাপ্ত

ব্যবহারকারী প্রয়োজনীয় কার্যকলাপ স্বীকৃতির অনুমতি দেওয়ার পরে, আপনি স্টেপ কাউন্টার সেন্সর অ্যাক্সেস করতে পারেন:

  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 ইন্টারফেস কীভাবে প্রয়োগ করতে হয় তা দেখায়।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 হল সেই উপাদান যা নিশ্চিতভাবে কার্যকর করার জন্য ব্যাকগ্রাউন্ডের কাজ করে। 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. Configuration.Provider ইন্টারফেস বাস্তবায়ন করতে Application ক্লাস প্রসারিত করুন।
  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" />