ব্যায়ামের রুট যোগ করা

এই নির্দেশিকা Health Connect-এর 1.1.0-alpha12 ভার্সনের সাথে মানানসই।

এক্সারসাইজ রুট ব্যবহারকারীদের সংশ্লিষ্ট এক্সারসাইজ অ্যাক্টিভিটির জন্য GPS রুট ট্র্যাক করতে এবং অন্যান্য অ্যাপের সাথে তাদের ওয়ার্কআউটের ম্যাপ শেয়ার করতে দেয়।

Health Connect-এর উপলভ্যতা চেক করা

Health Connect ব্যবহার করার চেষ্টা করার আগে, আপনার অ্যাপকে যাচাই করে নিতে হবে যে ব্যবহারকারীর ডিভাইসে Health Connect উপলভ্য আছে কিনা। সব ডিভাইসে Health Connect আগে থেকে ইনস্টল করা নাও থাকতে পারে অথবা এটি বন্ধ করা থাকতে পারে। আপনি HealthConnectClient.getSdkStatus() পদ্ধতি ব্যবহার করে উপলভ্যতা চেক করতে পারবেন।

Health Connect উপলভ্য আছে কিনা কীভাবে চেক করবেন

fun checkHealthConnectAvailability(context: Context) {
    val providerPackageName = "com.google.android.apps.healthdata" // Or get from HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME
    val availabilityStatus = HealthConnectClient.getSdkStatus(context, providerPackageName)

    if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE) {
      // Health Connect is not available. Guide the user to install/enable it.
      // For example, show a dialog.
      return // early return as there is no viable integration
    }
    if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED) {
      // Health Connect is available but requires an update.
      // Optionally redirect to package installer to find a provider, for example:
      val uriString = "market://details?id=$providerPackageName&url=healthconnect%3A%2F%2Fonboarding"
      context.startActivity(
        Intent(Intent.ACTION_VIEW).apply {
          setPackage("com.android.vending")
          data = Uri.parse(uriString)
          putExtra("overlay", true)
          putExtra("callerId", context.packageName)
        }
      )
      return
    }
    // Health Connect is available, obtain a HealthConnectClient instance
    val healthConnectClient = HealthConnectClient.getOrCreate(context)
    // Issue operations with healthConnectClient
}

getSdkStatus()-এর থেকে পাওয়া স্ট্যাটাসের উপর নির্ভর করে, আপনি ব্যবহারকারীকে প্রয়োজন হলে Google Play Store থেকে Health Connect ইনস্টল বা আপডেট করার ব্যাপারে গাইড করতে পারবেন।

এই নির্দেশিকা থেকে ব্যবহারকারীর কাছে কীভাবে অনুমতি চাওয়া যায় সেই বিষয়ে তথ্য পাওয়া যায় এবং এছাড়াও, এক্সারসাইজ সেশনের অংশ হিসেবে অ্যাপ কীভাবে রুট ডেটা লেখার অনুমতি পায় সেই বিষয়েও বর্ণনা করা আছে।

ব্যায়ামের রুট সংক্রান্ত তথ্য পড়া ও লেখার ফাংশনের মধ্যে এগুলি অন্তর্ভুক্ত:

  1. অ্যাপ শরীরচর্চার রুট সংক্রান্ত তথ্য লেখার জন্য নতুন অনুমতি তৈরি করে।
  2. ফিল্ড হিসেবে রুট সহ ব্যায়াম সেশন লিখে ইনসার্ট করা হয়।
  3. রিডিং:
    1. সেশনের মালিকের জন্য, সেশন রিড ব্যবহার করে ডেটা অ্যাক্সেস করা হয়।
    2. থার্ড-পার্টি অ্যাপ থেকে, একটি ডায়ালগের মাধ্যমে যা ব্যবহারকারীকে কোনও রুটের একবার পড়ার অনুমতি দেয়।

ব্যবহারকারীর কাছে লেখার অনুমতি না থাকলে এবং রুট সেট করা না থাকলে, রুট আপডেট হয় না।

আপনার অ্যাপের যদি রুট লেখার অনুমতি থাকে এবং সেটি যদি রুট ছাড়া সেশন অবজেক্ট পাস করে সেশন আপডেট করার চেষ্টা করে, তাহলে আগে থেকে থাকা রুট মুছে যায়।

ফিচারের উপলভ্যতা

ব্যবহারকারীর ডিভাইসে Health Connect-এ প্ল্যান করা ব্যায়াম কাজ করে কিনা তা নির্ধারণ করতে, ক্লায়েন্টে FEATURE_PLANNED_EXERCISE-এর উপলভ্যতা চেক করুন:

if (healthConnectClient
     .features
     .getFeatureStatus(
       HealthConnectFeatures.FEATURE_PLANNED_EXERCISE
     ) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE) {

  // Feature is available
} else {
  // Feature isn't available
}
আরও জানতে ফিচারের উপলভ্যতা চেক করুন দেখুন।

প্রয়োজনীয় অনুমতি

এক্সারসাইজ রুটের অ্যাক্সেস নিম্নলিখিত অনুমতি দ্বারা সুরক্ষিত থাকে:

  • android.permission.health.READ_EXERCISE_ROUTES
  • android.permission.health.WRITE_EXERCISE_ROUTE
মনে রাখবেন: এই ধরনের অনুমতির ক্ষেত্রে, READ_EXERCISE_ROUTES হল বহুবচন, আর WRITE_EXERCISE_ROUTE হল একবচন।

আপনার অ্যাপে ব্যায়ামের রুট সংক্রান্ত সুবিধা যোগ করতে, ExerciseSession ডেটা টাইপের জন্য অনুমতি চেয়ে শুরু করুন।

শরীরচর্চার রুট লেখার জন্য আপনাকে এই অনুমতি ঘোষণা করতে হবে:

<application>
  <uses-permission
android:name="android.permission.health.WRITE_EXERCISE_ROUTE" />
...
</application>

শরীরচর্চার রুট পড়তে, আপনাকে নিম্নলিখিত অনুমতিগুলির জন্য অনুরোধ করতে হবে:

<application>
  <uses-permission
android:name="android.permission.health.READ_EXERCISE_ROUTES" />
...
</application>

এছাড়াও, আপনাকে ব্যায়ামের অনুমতি ঘোষণা করতে হবে, কারণ প্রতিটি রুট একটি ব্যায়াম সেশনের সাথে যুক্ত থাকে (একটি সেশন = একটি ওয়ার্কআউট)।

অনুমতির অনুরোধ করতে, Health Connect-এর সাথে আপনার অ্যাপ প্রথমবার কানেক্ট করার সময় PermissionController.createRequestPermissionResultContract() পদ্ধতি ব্যবহার করুন। আপনি অনুরোধ করতে পারেন এমন একাধিক অনুমতি হল:

  • রুট ডেটা সহ স্বাস্থ্য ও ফিটনেস ডেটা পড়ুন: HealthPermission.getReadPermission(ExerciseSessionRecord::class)
  • রুট ডেটা সহ স্বাস্থ্য ও ফিটনেস ডেটা লেখা: HealthPermission.getWritePermission(ExerciseSessionRecord::class)
  • ব্যায়ামের রুট সংক্রান্ত ডেটা লেখা: HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE

ব্যবহারকারীর থেকে অনুমতি চাওয়া

ক্লায়েন্ট ইনস্ট্যান্স তৈরি করার পরে, আপনার অ্যাপকে ব্যবহারকারীর কাছে অনুমতি চেয়ে অনুরোধ করতে হবে। ব্যবহারকারীদের যেকোনও সময় অনুমতি দেওয়া বা প্রত্যাহার করার সুবিধা দিতে হবে। এটি করতে, প্রয়োজনীয় ডেটার ধরনের জন্য অনুমতির একটি সেট তৈরি করুন। সেটে থাকা অনুমতিগুলি প্রথমে আপনার Android মেনিফেস্টে ঘোষণা করা হয়েছে কিনা তা ভালো করে দেখে নিন।

val permissions =
    setOf(
        HealthPermission.getReadPermission(ExerciseSessionRecord::class),
        HealthPermission.getWritePermission(ExerciseSessionRecord::class)
    )
আপনার অ্যাপে আগে থেকেই প্রয়োজনীয় অনুমতি দেওয়া আছে কিনা তা দেখতে getGrantedPermissions ব্যবহার করুন। তা না হলে, সেইসব অনুমতির অনুরোধ করতে createRequestPermissionResultContract ব্যবহার করুন। এটি Health Connect-এর অনুমতি সংক্রান্ত স্ক্রিন দেখায়।
val permissions = setOf(
        HealthPermission.getReadPermission(StepsRecord::class),
        HealthPermission.getWritePermission(StepsRecord::class),
        HealthPermission.getReadPermission(HeartRateRecord::class),
        HealthPermission.getWritePermission(HeartRateRecord::class)
    )

val requestPermissionsLauncher = rememberLauncherForActivityResult(
    contract = PermissionController.createRequestPermissionResultContract()
) { grantedPermissions ->
    if (grantedPermissions.containsAll(permissions)) {
        coroutineScope.launch { snackbarHostState.showSnackbar("Permissions granted!") }
    } else {
        coroutineScope.launch { snackbarHostState.showSnackbar("Permissions denied.") }
    }
}
যেহেতু ব্যবহারকারীরা যেকোনও সময় অনুমতি দিতে বা প্রত্যাহার করতে পারেন, তাই আপনার অ্যাপকে প্রতিবার অনুমতি ব্যবহার করার আগে তা চেক করতে হবে এবং অনুমতি প্রত্যাহার করা হলে সেই পরিস্থিতি সামলাতে হবে।

এক্সারসাইজ সেশন রেকর্ডে অন্তর্ভুক্ত তথ্য

প্রতিটি এক্সারসাইজ সেশন রেকর্ডে নিম্নলিখিত তথ্য থাকে:

  • ব্যায়ামের ধরন, যেমন, সাইকেল চালানো।
  • এক্সারসাইজ রুট, যার মধ্যে এই ধরনের তথ্য থাকে: অক্ষাংশ, দ্রাঘিমাংশ ও উচ্চতা।

যেসব এগ্রিগেশন কাজ করে

ExerciseSessionRecord-এর জন্য নিম্নলিখিত অ্যাগ্রিগেট ভ্যালু উপলভ্য:

ব্যবহারের উদাহরণ

নিচের কোড স্নিপেট থেকে কীভাবে ব্যায়ামের রুট পড়তে ও লিখতে হয় তা জানতে পারবেন।

ব্যায়ামের ডেটা দেখুন

আপনার অ্যাপ ব্যাকগ্রাউন্ডে চললে, অন্যান্য অ্যাপের তৈরি করা এক্সারসাইজ রুট ডেটা পড়তে পারে না।

আপনার অ্যাপ ব্যাকগ্রাউন্ডে রান করলে এবং অন্য কোনও অ্যাপের তৈরি করা এক্সারসাইজ রুট রিড করার চেষ্টা করলে, Health Connect একটি ExerciseRouteResult.ConsentRequired উত্তর দেয়, এমনকি আপনার অ্যাপের কাছে এক্সারসাইজ রুট ডেটা অ্যাক্সেস করার সর্বদা অনুমতি দিন বিকল্প থাকলেও।

এই কারণে, আমরা দৃঢ়ভাবে সাজেস্ট করি যে আপনার অ্যাপের সাথে ব্যবহারকারীর ইচ্ছাকৃত ইন্টার‍্যাকশন হলে এবং ব্যবহারকারী যখন আপনার অ্যাপের UI-এর সাথে সক্রিয়ভাবে এনগেজ থাকেন, তখনই রুট অনুরোধ করুন।

ব্যাকগ্রাউন্ড রিড সম্পর্কে আরও জানতে, ব্যাকগ্রাউন্ড রিডের উদাহরণ দেখুন।

নিচে দেওয়া কোড স্নিপেট থেকে Health Connect-এ সেশন কীভাবে রিড করতে হয় এবং সেই সেশন থেকে কীভাবে রুট সংক্রান্ত অনুরোধ করতে হয় তা জানতে পারবেন:

private suspend fun readExerciseSessionAndRoute() {
    val client = healthConnectClient ?: return

    val endTime = Instant.now()
    val startTime = endTime.minus(Duration.ofHours(1))

    val grantedPermissions = client.permissionController.getGrantedPermissions()

    // 1. Verify basic Exercise Session permissions
    if (!grantedPermissions.contains(
            HealthPermission.getReadPermission(ExerciseSessionRecord::class)
        )
    ) {
        return
    }

    // 2. Read the sessions
    val readResponse = client.readRecords(
        ReadRecordsRequest(
            ExerciseSessionRecord::class,
            TimeRangeFilter.between(startTime, endTime)
        )
    )

    val exerciseRecord = readResponse.records.firstOrNull() ?: return
    val recordId = exerciseRecord.metadata.id

    // 3. Read the specific record to check for the route
    val sessionResponse = client.readRecord(ExerciseSessionRecord::class, recordId)

    // 4. Handle the Route Result directly from the response
    when (val routeResult = sessionResponse.record.exerciseRouteResult) {
        is ExerciseRouteResult.Data -> {
            displayExerciseRoute(routeResult.exerciseRoute)
        }
        is ExerciseRouteResult.ConsentRequired -> {
            // Since you are in a Service, you cannot launch ActivityResultLauncher.
            // Send a notification to the user to grant route-specific consent.
            handleConsentRequired(recordId)
        }
        is ExerciseRouteResult.NoData -> Unit
        else -> Unit
    }
}

private fun displayExerciseRoute(route: ExerciseRoute) {
    val locations = route.route.orEmpty()
    for (location in locations) {
        println(location)
    }
}

ব্যায়ামের রুট লেখা

নিচের কোড থেকে বোঝা যাবে যে কীভাবে এমন সেশন রেকর্ড করতে হয় যাতে এক্সারসাইজ রুট অন্তর্ভুক্ত থাকে:

private suspend fun insertExerciseRoute() {
    val client = healthConnectClient ?: return

    val grantedPermissions = client.permissionController.getGrantedPermissions()

    // 1. Verify Session Write Permission
    val hasWriteSession = grantedPermissions.contains(
        HealthPermission.getWritePermission(ExerciseSessionRecord::class)
    )
    if (!hasWriteSession) return

    val sessionStartTime = Instant.now()
    val sessionDuration = Duration.ofMinutes(20)
    val sessionEndTime = sessionStartTime.plus(sessionDuration)

    // 2. Build the route if route-specific write permission is granted
    val hasWriteRoute = grantedPermissions.contains(HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE)

    val exerciseRoute = if (hasWriteRoute) {
        ExerciseRoute(
            listOf(
                ExerciseRoute.Location(
                    time = sessionStartTime,
                    latitude = 6.5483,
                    longitude = 0.5488,
                    horizontalAccuracy = Length.meters(2.0),
                    verticalAccuracy = Length.meters(2.0),
                    altitude = Length.meters(9.0),
                ),
                ExerciseRoute.Location(
                    time = sessionEndTime.minusSeconds(1),
                    latitude = 6.4578,
                    longitude = 0.6577,
                    horizontalAccuracy = Length.meters(2.0),
                    verticalAccuracy = Length.meters(2.0),
                    altitude = Length.meters(9.2),
                )
            )
        )
    } else {
        null
    }

    // 3. Create the session record
    val exerciseSessionRecord = ExerciseSessionRecord(
        startTime = sessionStartTime,
        startZoneOffset = ZoneOffset.UTC,
        endTime = sessionEndTime,
        endZoneOffset = ZoneOffset.UTC,
        exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_BIKING,
        title = "Morning Bike Ride",
        exerciseRoute = exerciseRoute,
        metadata = Metadata.activelyRecorded(
            device = Device(type = Device.TYPE_PHONE)
        )
    )

    // 4. Insert into Health Connect
    client.insertRecords(listOf(exerciseSessionRecord))
}

ব্যায়াম সেশন

দৌড়ানো থেকে ব্যাডমিন্টন পর্যন্ত যেকোনও কিছু ব্যায়াম সেশনে অন্তর্ভুক্ত থাকতে পারে।

ব্যায়াম সেশন সংক্রান্ত ডেটা লেখা

সেশন সহ একটি ইনসার্শন অনুরোধ কীভাবে তৈরি করতে হয় তা এখানে দেওয়া হল:

suspend fun writeExerciseSession(healthConnectClient: HealthConnectClient) {
    healthConnectClient.insertRecords(
        listOf(
            ExerciseSessionRecord(
                startTime = START_TIME,
                startZoneOffset = START_ZONE_OFFSET,
                endTime = END_TIME,
                endZoneOffset = END_ZONE_OFFSET,
                exerciseType = ExerciseSessionRecord.ExerciseType.RUNNING,
                title = "My Run",
                metadata = Metadata.manualEntry()
            ),
            // ... other records
        )
    )
}

ব্যায়াম সেশন সংক্রান্ত ডেটা পড়া

এক্সারসাইজ সেশন কীভাবে পড়তে হয় তার একটি উদাহরণ এখানে দেওয়া হল:

suspend fun readExerciseSessions(
    healthConnectClient: HealthConnectClient,
    startTime: Instant,
    endTime: Instant
) {
    val response =
        healthConnectClient.readRecords(
            ReadRecordsRequest(
                ExerciseSessionRecord::class,
                timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
            )
        )
    for (exerciseRecord in response.records) {
        // Process each exercise record
        // Optionally pull in with other data sources of the same time range.
        val distanceRecord =
            healthConnectClient
                .readRecords(
                    ReadRecordsRequest(
                        DistanceRecord::class,
                        timeRangeFilter =
                            TimeRangeFilter.between(
                                exerciseRecord.startTime,
                                exerciseRecord.endTime
                            )
                    )
                )
                .records
    }
}

সাবটাইপ ডেটা লেখা

সেশনে ঐচ্ছিক সাবটাইপ ডেটাও থাকতে পারে, যা অতিরিক্ত তথ্য দিয়ে সেশনকে আরও সমৃদ্ধ করে।

যেমন, ব্যায়াম সেশনে ExerciseSegment, ExerciseLap ও ExerciseRoute ক্লাস অন্তর্ভুক্ত থাকতে পারে:

val segments = listOf(
  ExerciseSegment(
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
    segmentType = ActivitySegmentType.BENCH_PRESS,
    repetitions = 373
  )
)

val laps = listOf(
  ExerciseLap(
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
    length = 0.meters
  )
)

ExerciseSessionRecord(
  exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_CALISTHENICS,
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
  startZoneOffset = ZoneOffset.UTC,
  endZoneOffset = ZoneOffset.UTC,
  segments = segments,
  laps = laps,
  route = route,
  metadata = Metadata.manualEntry()
)

ব্যায়ামের সেশন মুছে দেওয়া

দুটি উপায়ে এক্সারসাইজ সেশন মোছা যায়:

  1. সময়সীমা অনুযায়ী।
  2. UID দিয়ে।

সময়সীমা অনুযায়ী কীভাবে সাবটাইপ ডেটা মুছবেন তা এখানে দেওয়া হল:

suspend fun deleteExerciseSessionByTimeRange(
    healthConnectClient: HealthConnectClient,
    exerciseRecord: ExerciseSessionRecord,
) {
    val timeRangeFilter = TimeRangeFilter.between(exerciseRecord.startTime, exerciseRecord.endTime)
    healthConnectClient.deleteRecords(ExerciseSessionRecord::class, timeRangeFilter)
    // delete the associated distance record
    healthConnectClient.deleteRecords(DistanceRecord::class, timeRangeFilter)
}

এছাড়াও, আপনি UID-এর মাধ্যমে সাবটাইপ ডেটা মুছে দিতে পারবেন। এটি করলে শুধু এক্সারসাইজ সেশন মুছে যায়, সংশ্লিষ্ট ডেটা নয়:

suspend fun deleteExerciseSessionByUid(
    healthConnectClient: HealthConnectClient,
    exerciseRecord: ExerciseSessionRecord,
) {
    healthConnectClient.deleteRecords(
        ExerciseSessionRecord::class,
        recordIdsList = listOf(exerciseRecord.metadata.id),
        clientRecordIdsList = emptyList()
    )
}