Wear-এ ডেটা লেয়ার ইভেন্টগুলি পরিচালনা করুন

আপনি যখন ডেটা লেয়ার এপিআই-তে কোনো কল করেন, সেটি সম্পন্ন হলে আপনি কলটির স্ট্যাটাস জানতে পারবেন। এছাড়াও, Wear OS by Google নেটওয়ার্কের যেকোনো স্থানে আপনার অ্যাপের করা ডেটা পরিবর্তনের ফলে সৃষ্ট ডেটা ইভেন্টগুলোও আপনি শুনতে পারবেন।

ডেটা লেয়ার এপিআই কার্যকরভাবে ব্যবহার করার একটি উদাহরণের জন্য অ্যান্ড্রয়েড ডেটালেয়ার স্যাম্পল অ্যাপটি দেখুন।

ডেটা লেয়ার কলগুলির স্থিতির জন্য অপেক্ষা করুন

ডেটা লেয়ার এপিআই-তে করা কল—যেমন DataClient ক্লাসের putDataItem মেথড ব্যবহার করে করা কল—কখনও কখনও একটি Task<ResultType> অবজেক্ট রিটার্ন করে। Task অবজেক্টটি তৈরি হওয়ার সাথে সাথেই অপারেশনটি ব্যাকগ্রাউন্ডে কিউ-তে যুক্ত হয়ে যায়। এরপর আপনি যদি আর কিছু না করেন, তাহলে অপারেশনটি অবশেষে নীরবে সম্পন্ন হয়ে যায়।

তবে, অপারেশনটি সম্পন্ন হওয়ার পর আপনি সাধারণত ফলাফলটি নিয়ে কিছু করতে চান, তাই Task অবজেক্টটি আপনাকে অ্যাসিঙ্ক্রোনাসলি বা সিঙ্ক্রোনাসলি ফলাফলের স্ট্যাটাসের জন্য অপেক্ষা করার সুযোগ দেয়।

অ্যাসিঙ্ক্রোনাস কল

আপনার কোড যদি প্রধান UI থ্রেডে চলে, তাহলে ডেটা লেয়ার API-তে ব্লকিং কল করবেন না এবং putDataItem কল করার জন্য একটি কো-রুটিন ব্যবহার করুন:

private suspend fun Context.sendDataAsync(count: Int) {
    try {
        val putDataReq: PutDataRequest = PutDataMapRequest.create("/count").run {
            dataMap.putInt("count_key", count)
            asPutDataRequest()
        }
        val dataItem = Wearable.getDataClient(this).putDataItem(putDataReq).await()
        handleDataItem(dataItem)
    } catch (e: Exception) {
        handleDataItemError(e)
    } finally {
        handleTaskComplete()
    }
}

private fun handleDataItem(dataItem: DataItem) { }
private fun handleDataItemError(exception: Exception) { }
private fun handleTaskComplete() { }

বিভিন্ন টাস্কের নির্বাহকে শৃঙ্খলিত করা সহ অন্যান্য সম্ভাবনার জন্য টাস্ক এপিআই (Task API) দেখুন।

সিঙ্ক্রোনাস কল

আপনার কোড যদি কোনো ব্যাকগ্রাউন্ড সার্ভিসের (যেমন WearableListenerService ) একটি আলাদা হ্যান্ডলার থ্রেডে চলে, তাহলে putDataItem কে একটি ব্লকিং কল করার জন্য runBlocking ব্যবহার করুন।

দ্রষ্টব্য: মূল থ্রেডে থাকাকালীন এটি কল করবেন না।

private fun Context.sendDataSync(count: Int) = runBlocking {
    val putDataReq = PutDataMapRequest.create("/count").run {
        dataMap.putInt("count_key", count)
        asPutDataRequest()
    }

    try {
        val result = Wearable.getDataClient(this@sendDataSync)
            .putDataItem(putDataReq)
            .await()
        // Logic for success
    } catch (e: Exception) {
        // Handle failure
    }
}

ডেটা লেয়ার ইভেন্টগুলির জন্য শুনুন

যেহেতু ডেটা লেয়ার হ্যান্ডহেল্ড এবং পরিধানযোগ্য ডিভাইসগুলোর মধ্যে ডেটা সিঙ্ক্রোনাইজ করে ও পাঠায়, তাই সাধারণত ডেটা আইটেম তৈরি হওয়া এবং মেসেজ গ্রহণ করার মতো গুরুত্বপূর্ণ ইভেন্টগুলোর জন্য নজর রাখতে হয়।

ডেটা লেয়ার ইভেন্ট শোনার জন্য আপনার কাছে দুটি বিকল্প আছে:

  • একটি সার্ভিস তৈরি করুন যা WearableListenerService এক্সটেন্ড করে।
  • এমন একটি অ্যাক্টিভিটি বা ক্লাস তৈরি করুন যা DataClient.OnDataChangedListener ইন্টারফেসটি ইমপ্লিমেন্ট করে।

এই উভয় বিকল্পের মাধ্যমেই, আপনি যে ইভেন্টগুলো পরিচালনা করতে আগ্রহী, সেগুলোর জন্য ডেটা ইভেন্ট কলব্যাক মেথডগুলোকে ওভাররাইড করতে পারেন।

দ্রষ্টব্য: লিসেনার ইমপ্লিমেন্টেশন বেছে নেওয়ার সময় আপনার অ্যাপের ব্যাটারি ব্যবহারের বিষয়টি বিবেচনা করুন। একটি WearableListenerService অ্যাপের ম্যানিফেস্টে রেজিস্টার করা হয় এবং অ্যাপটি আগে থেকে চালু না থাকলে এটি সেটিকে চালু করতে পারে। যদি আপনার শুধু তখনই ইভেন্ট শোনার প্রয়োজন হয় যখন আপনার অ্যাপটি আগে থেকেই চালু থাকে, যা ইন্টারেক্টিভ অ্যাপ্লিকেশনগুলোর ক্ষেত্রে প্রায়শই ঘটে থাকে, তাহলে WearableListenerService ব্যবহার করবেন না। এর পরিবর্তে, একটি লাইভ লিসেনার রেজিস্টার করুন। উদাহরণস্বরূপ, DataClient ক্লাসের addListener মেথডটি ব্যবহার করুন। এটি সিস্টেমের উপর চাপ এবং ব্যাটারি ব্যবহার কমাতে পারে।

একটি পরিধানযোগ্য লিসেনার পরিষেবা ব্যবহার করুন

আপনি সাধারণত আপনার ওয়্যারেবল এবং হ্যান্ডহেল্ড উভয় অ্যাপেই WearableListenerService এর ইনস্ট্যান্স তৈরি করেন। তবে, যদি আপনি কোনো একটি অ্যাপের ডেটা ইভেন্টে আগ্রহী না হন, তাহলে সেই অ্যাপে সার্ভিসটি ইমপ্লিমেন্ট করার প্রয়োজন নেই।

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

WearableListenerService ব্যবহার করে আপনি যেসব ইভেন্ট শুনতে পারেন, তার মধ্যে কয়েকটি হলো নিচে দেওয়া হলো:

  • onDataChanged() : যখনই কোনো ডেটা আইটেম অবজেক্ট তৈরি, মুছে ফেলা বা পরিবর্তন করা হয়, সিস্টেম সমস্ত সংযুক্ত নোডে এই কলব্যাকটি ট্রিগার করে।
  • onMessageReceived() : কোনো নোড থেকে পাঠানো বার্তা টার্গেট নোডে এই কলব্যাকটি ট্রিগার করে।
  • onCapabilityChanged() : যখন আপনার অ্যাপের কোনো ইনস্ট্যান্সের বিজ্ঞাপিত কোনো ক্যাপাবিলিটি নেটওয়ার্কে উপলব্ধ হয়, তখন সেই ইভেন্টটি এই কলব্যাকটি ট্রিগার করে। আপনি যদি কাছাকাছি কোনো নোড খুঁজতে চান, তাহলে কলব্যাকে প্রদত্ত নোডগুলোর isNearby() মেথডটি কোয়েরি করতে পারেন।

আপনি ChannelClient.ChannelCallback থেকে onChannelOpened() এর মতো ইভেন্টগুলোও শুনতে পারেন।

পূর্ববর্তী সমস্ত ঘটনা মূল থ্রেডে নয়, বরং একটি ব্যাকগ্রাউন্ড থ্রেডে সম্পাদিত হয়।

একটি WearableListenerService তৈরি করতে, এই ধাপগুলো অনুসরণ করুন:

  1. একটি ক্লাস তৈরি করুন যা WearableListenerService এক্সটেন্ড করে।
  2. আপনার আগ্রহের ইভেন্টগুলো, যেমন onDataChanged() , শোনার জন্য অপেক্ষা করুন।
  3. আপনার WearableListenerService সম্পর্কে সিস্টেমকে অবহিত করতে আপনার অ্যান্ড্রয়েড ম্যানিফেস্টে একটি ইন্টেন্ট ফিল্টার ঘোষণা করুন। এই ঘোষণাটি সিস্টেমকে প্রয়োজন অনুযায়ী আপনার সার্ভিসটি বাইন্ড করার সুযোগ দেয়।

নিম্নলিখিত উদাহরণটি দেখায় কিভাবে একটি WearableListenerService প্রয়োগ করতে হয়:

class DataLayerListenerService : WearableListenerService() {

    override fun onDataChanged(dataEvents: DataEventBuffer) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "onDataChanged: $dataEvents")
        }

        // Loop through the events and send a message
        // to the node that created the data item.
        dataEvents
            .map { it.dataItem.uri }
            .forEach { uri ->
                // Get the node ID from the host value of the URI.
                val nodeId: String = uri.host!!
                // Set the data of the message to be the bytes of the URI.
                val payload: ByteArray = uri.toString().toByteArray()

                // Send the RPC.
                Wearable.getMessageClient(this)
                    .sendMessage(
                        nodeId,
                        DATA_ITEM_RECEIVED_PATH,
                        payload
                    )
            }
    }
}

পরবর্তী অংশে এই লিসেনারের সাথে ইন্টেন্ট ফিল্টার কীভাবে ব্যবহার করতে হয় তা ব্যাখ্যা করা হয়েছে।

WearableListenerService-এর সাথে ফিল্টার ব্যবহার করুন

পূর্ববর্তী বিভাগে দেখানো WearableListenerService উদাহরণটির জন্য একটি ইন্টেন্ট ফিল্টার দেখতে এইরকম হতে পারে:

<service
    android:name=".snippets.datalayer.DataLayerListenerService"
    android:exported="true"
    tools:ignore="ExportedService" >
    <intent-filter>
        <action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
        <data
            android:scheme="wear"
            android:host="*"
            android:path="/start-activity" />
    </intent-filter>
</service>

DATA_CHANGED অ্যাকশন ফিল্টারটি সিস্টেমকে জানায় যে আপনার অ্যাপ ডেটা লেয়ার ইভেন্টগুলোতে আগ্রহী।

এই উদাহরণে, ঘড়িটি /start-activity ডেটা আইটেমের জন্য শোনে, এবং ফোনটি /data-item-received ( DATA_ITEM_RECEIVED_PATH ) মেসেজ রেসপন্সের জন্য শোনে।

সাধারণ অ্যান্ড্রয়েড ফিল্টার মেলানোর নিয়ম প্রযোজ্য। আপনি প্রতি ম্যানিফেস্টে একাধিক সার্ভিস, প্রতি সার্ভিসে একাধিক ইন্টেন্ট ফিল্টার, প্রতি ফিল্টারে একাধিক অ্যাকশন এবং প্রতি ফিল্টারে একাধিক ডেটা স্ট্যানজা নির্দিষ্ট করতে পারেন। ফিল্টারগুলো একটি ওয়াইল্ডকার্ড হোস্টে অথবা একটি নির্দিষ্ট হোস্টে ম্যাচ করতে পারে। একটি ওয়াইল্ডকার্ড হোস্টে ম্যাচ করার জন্য, host="*" ব্যবহার করুন। একটি নির্দিষ্ট হোস্টে ম্যাচ করার জন্য, host=<node_id> উল্লেখ করুন।

আপনি একটি আক্ষরিক পাথ বা পাথ প্রিফিক্সও মেলাতে পারেন। এটি করার জন্য, আপনাকে অবশ্যই একটি ওয়াইল্ডকার্ড বা নির্দিষ্ট হোস্ট উল্লেখ করতে হবে। অন্যথায়, সিস্টেম আপনার উল্লেখ করা পাথটি উপেক্ষা করবে।

Wear OS যেসব ফিল্টার টাইপ সমর্থন করে সে সম্পর্কে আরও তথ্যের জন্য, WearableListenerService এর API রেফারেন্স ডকুমেন্টেশন দেখুন।

ডেটা ফিল্টার এবং ম্যাচিং নিয়ম সম্পর্কে আরও তথ্যের জন্য, <data> ম্যানিফেস্ট এলিমেন্টের API রেফারেন্স ডকুমেন্টেশন দেখুন।

ইনটেন্ট ফিল্টার মেলানোর সময় দুটি গুরুত্বপূর্ণ নিয়ম মনে রাখবেন:

  • ইনটেন্ট ফিল্টারের জন্য কোনো স্কিম নির্দিষ্ট করা না থাকলে, সিস্টেম অন্য সব URI অ্যাট্রিবিউট উপেক্ষা করে।
  • ফিল্টারের জন্য কোনো হোস্ট নির্দিষ্ট করা না থাকলে, সিস্টেম সমস্ত পাথ অ্যাট্রিবিউট উপেক্ষা করে।

একটি লাইভ লিসেনার ব্যবহার করুন

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

আরও পরিচ্ছন্ন এবং নিরাপদ পদ্ধতির জন্য, একটি লাইফসাইকেল অবজারভার ব্যবহার করুন। লাইফসাইকেল অবজারভার ব্যবহার করে, আপনি রেজিস্ট্রেশন লজিককে অ্যাক্টিভিটির LifecycleResumeEvent থেকে সরিয়ে একটি পৃথক, পুনঃব্যবহারযোগ্য ক্লাসে নিয়ে যান, যা DefaultLifecycleObserver ইমপ্লিমেন্ট করে।

এই পদ্ধতি আপনার অ্যাক্টিভিটিকে হালকা রাখে এবং লিসেনার আনরেজিস্টার করতে ভুলে যাওয়ার মতো সাধারণ বাগ প্রতিরোধ করে।

১. লাইফসাইকেল-সচেতন লিসেনার তৈরি করুন

এই ক্লাসটি DataClient.OnDataChangedListener র‍্যাপ করে এবং অ্যাক্টিভিটির লাইফসাইকেলের উপর ভিত্তি করে স্বয়ংক্রিয়ভাবে এর নিজস্ব সাবস্ক্রিপশন পরিচালনা করে।

class WearDataLayerObserver(
    private val dataClient: DataClient,
    private val onDataReceived: (DataEventBuffer) -> Unit
) : DefaultLifecycleObserver, DataClient.OnDataChangedListener {

    // Implementation of the DataClient listener
    override fun onDataChanged(dataEvents: DataEventBuffer) {
        onDataReceived(dataEvents)
    }

    // Automatically register when the Activity starts
    override fun onResume(owner: LifecycleOwner) {
        dataClient.addListener(this)
    }

    // Automatically unregister when the Activity pauses
    override fun onPause(owner: LifecycleOwner) {
        dataClient.removeListener(this)
    }
}

২. আপনার কার্যকলাপে ব্যবহার

এখন, আপনার অ্যাক্টিভিটিতে Wear API-এর জন্য LifecycleResumeEvent বা onPause ব্যবহার করার প্রয়োজন নেই। আপনি LaunchedEvent (বা onCreate )-এ একবার অবজারভারটি রেজিস্টার করবেন।

class DataLayerLifecycleActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val dataClient = Wearable.getDataClient(this)

        // Create the observer and link it to the activity's lifecycle
        val wearObserver = WearDataLayerObserver(dataClient) { dataEvents ->
            handleDataEvents(dataEvents)
        }

        lifecycle.addObserver(wearObserver)
    }

    private fun handleDataEvents(dataEvents: DataEventBuffer) {
        // ... filter and process events ...
    }
}

কেন এটি আরও ভালো:

  • ক্লিনার অ্যাক্টিভিটি: আপনি অ্যাক্টিভিটি লাইফসাইকেল মেথডগুলো থেকে বয়লারপ্লেট অপসারণ করেন।
  • নিরাপত্তা: DefaultLifecycleObserver অপ্রত্যাশিতভাবে অ্যাক্টিভিটি ধ্বংস হয়ে গেলেও লিসেনারটি অপসারিত হয়েছে কিনা তা যাচাই করতে সাহায্য করে, ফলে মেমোরি লিক প্রতিরোধ করা যায়।
  • পুনঃব্যবহারযোগ্যতা: আপনি রেজিস্ট্রেশন লজিক পুনরায় না লিখেই এই WearDataLayerObserver টিকে যেকোনো Activity বা Composable-এ যুক্ত করতে পারেন।
  • বিচ্ছিন্নকরণ: কখন শুনতে হবে সেই যুক্তি, ডেটা নিয়ে কী করতে হবে সেই যুক্তি থেকে পৃথক করা হয়।

লাইভ লিসেনারদের সাথে ফিল্টার ব্যবহার করুন

পূর্বে যেমন উল্লেখ করা হয়েছে, ম্যানিফেস্ট-ভিত্তিক WearableListenerService অবজেক্টের জন্য যেমন আপনি ইন্টেন্ট ফিল্টার নির্দিষ্ট করতে পারেন, ঠিক তেমনি Wearable API-এর মাধ্যমে একটি লাইভ লিসেনার রেজিস্টার করার সময়ও আপনি ইন্টেন্ট ফিল্টার ব্যবহার করতে পারেন। API-ভিত্তিক লাইভ লিসেনার এবং ম্যানিফেস্ট-ভিত্তিক লিসেনার—উভয়ের ক্ষেত্রেই একই নিয়ম প্রযোজ্য।

একটি প্রচলিত পদ্ধতি হলো collectAsStateWithLifecycle() ব্যবহার করে একটি নির্দিষ্ট পাথ বা পাথ প্রিফিক্স সহ একটি লিসেনার রেজিস্টার করা । এইভাবে লিসেনার প্রয়োগ করার মাধ্যমে, আপনার অ্যাপ আরও বেছে বেছে ইভেন্ট গ্রহণ করতে পারে, যা এর ডিজাইন এবং কার্যকারিতা উন্নত করে।