কালেকশন উইজেট ব্যবহার করা

'কম্পোজ' ফিচার ব্যবহার করে দেখুন
Jetpack Compose হল Android-এর জন্য সাজেস্ট করা UI টুলকিট। Compose-স্টাইল API ব্যবহার করে কীভাবে উইজেট তৈরি করতে হয় তা জানুন।

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

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

ListView: উল্লম্বভাবে স্ক্রল করা যায় এমন তালিকায় আইটেম দেখানো ভিউ।

GridView: এমন একটি ভিউ যা দ্বিমাত্রিক স্ক্রল করা যায় এমন গ্রিডে আইটেম দেখায়।

StackView: একটি স্ট্যাক করা কার্ড ভিউ—এক ধরনের রোলডেক্স—যেখানে ব্যবহারকারী আগের বা পরের কার্ড দেখতে সামনের কার্ডটি উপরে বা নিচে ফ্লিক করতে পারেন, যথাক্রমে।

AdapterViewFlipper: একটি অ্যাডাপ্টার-ব্যাকড ViewAnimator যা দুটি বা তার বেশি ভিউয়ের মধ্যে অ্যানিমেট করে। একবারে শুধুমাত্র একটি সন্তানকে দেখানো হয়।

যেহেতু এই সংগ্রহ ভিউ, রিমোট ডেটা দ্বারা ব্যাক-আপ নেওয়া সংগ্রহ দেখায়, তাই সেগুলি ডেটার সাথে ব্যবহারকারীর ইন্টারফেস বাইন্ড করতে Adapter ব্যবহার করে। Adapter ডেটার সেট থেকে আলাদা আলাদা আইটেমকে আলাদা আলাদা View অবজেক্টের সাথে বাইন্ড করে।

এইসব কালেকশন ভিউ যেহেতু অ্যাডাপ্টরের মাধ্যমে পরিচালিত হয়, তাই উইজেটে এগুলি ব্যবহার করার জন্য Android ফ্রেমওয়ার্কে অতিরিক্ত আর্কিটেকচার থাকতে হবে। উইজেটের প্রেক্ষিতে, Adapter-এর পরিবর্তে RemoteViewsFactory ব্যবহার করা হয়, যা Adapter ইন্টারফেসের চারপাশে একটি পাতলা র‍্যাপার। সংগ্রহের মধ্যে থাকা কোনও নির্দিষ্ট আইটেমের জন্য অনুরোধ করা হলে, RemoteViewsFactory সেই আইটেমটি তৈরি করে RemoteViews অবজেক্ট হিসেবে সংগ্রহের জন্য রিটার্ন করে। আপনার উইজেটে একটি কালেকশন ভিউ যোগ করতে, RemoteViewsService ও RemoteViewsFactory প্রয়োগ করুন।

RemoteViewsService হল এমন একটি পরিষেবা যা রিমোট অ্যাডাপ্টারকে RemoteViews অবজেক্টের অনুরোধ করতে দেয়। RemoteViewsFactory হল একটি অ্যাডাপ্টারের ইন্টারফেস যা ListView, GridView ও StackView-এর মতো কালেকশন ভিউ এবং সেই ভিউয়ের অন্তর্নিহিত ডেটার মধ্যে কাজ করে। StackWidget স্যাম্পেল থেকে, এই পরিষেবা ও ইন্টারফেস প্রয়োগ করার জন্য এখানে বয়লারপ্লেট কোডের একটি উদাহরণ দেওয়া হল:

class StackWidgetService : RemoteViewsService() {
    override fun onGetViewFactory(intent: Intent): RemoteViewsFactory =
        StackRemoteViewsFactory(this.applicationContext, intent)
}

class StackRemoteViewsFactory(
    private val context: Context, intent: Intent
) : RemoteViewsService.RemoteViewsFactory {

    // See the RemoteViewsFactory API reference for the full list of methods to implement.

}

নমুনা অ্যাপ্লিকেশন

এই বিভাগে কোডের যে অংশগুলি দেখানো হয়েছে, সেগুলিও StackWidget নমুনা থেকে নেওয়া হয়েছে:

স্ট্যাক করা ভিউ দেখানো StackWidget
ছবি ১. StackWidget.

এই স্যাম্পেলে দশটি ভিউয়ের একটি স্ট্যাক আছে যা শূন্য থেকে নয় পর্যন্ত ভ্যালু দেখায়। নমুনা উইজেটে এইসব প্রাথমিক আচরণ থাকে:

  • পরবর্তী বা আগের ভিউ দেখতে ব্যবহারকারী উইজেটে উপরের ভিউ উল্লম্বভাবে ফ্লিং করতে পারবেন। এটি হল বিল্ট-ইন StackView আচরণ।

  • ব্যবহারকারীর কোনও ইন্টার‍্যাকশন ছাড়াই, উইজেটটি অটোমেটিক স্লাইডশোর মতো তার ভিউয়ের মাধ্যমে সিকোয়েন্সে এগিয়ে যায়। এটি android:autoAdvanceViewId="@id/stack_view" সেটিংয়ের কারণে res/xml/stackwidgetinfo.xml ফাইলে হয়। এই সেটিং ভিউ আইডির ক্ষেত্রে প্রযোজ্য, যা এই ক্ষেত্রে স্ট্যাক ভিউয়ের ভিউ আইডি।

  • ব্যবহারকারী সবচেয়ে উপরের ভিউ স্পর্শ করলে, উইজেট Toast মেসেজ দেখায়, "স্পর্শ করা ভিউ n," যেখানে n হল স্পর্শ করা ভিউয়ের ইন্ডেক্স (পজিশন)। কীভাবে আচরণ প্রয়োগ করতে হয় সেই বিষয়ে আরও আলোচনা করতে, আলাদা আলাদা আইটেমে আচরণ যোগ করুন বিভাগ দেখুন।

সংগ্রহের সাথে উইজেট প্রয়োগ করা

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

কালেকশন সহ উইজেটের ম্যানিফেস্ট

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

যেমন, RemoteViewsService ব্যবহার করে কোনও কালেকশন ভিউ পপুলেট করার জন্য উইজেট তৈরি করার সময়, ম্যানিফেস্ট এন্ট্রিটি এইরকম দেখতে হতে পারে:

<service android:name="MyWidgetService"
    android:permission="android.permission.BIND_REMOTEVIEWS" />

এই উদাহরণে, android:name="MyWidgetService" আপনার RemoteViewsService-এর সাবক্লাসকে বোঝায়।

সংগ্রহ সহ উইজেটের লেআউট

আপনার উইজেট লেআউট XML ফাইলের মূল প্রয়োজনীয়তা হল, এতে একটি কালেকশন ভিউ থাকতে হবে: ListView, GridView, StackView অথবা AdapterViewFlipper। StackWidget নমুনা-এর জন্য widget_layout.xml ফাইল এখানে দেওয়া হল:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <StackView
        android:id="@+id/stack_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:loopViews="true" />
    <TextView
        android:id="@+id/empty_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:background="@drawable/widget_item_background"
        android:textColor="#ffffff"
        android:textStyle="bold"
        android:text="@string/empty_view_text"
        android:textSize="20sp" />
</FrameLayout>

মনে রাখবেন, খালি ভিউ অবশ্যই কালেকশন ভিউয়ের সিবলিং হতে হবে, যার জন্য খালি ভিউ খালি অবস্থা দেখায়।

আপনার সম্পূর্ণ উইজেটের লেআউট ফাইলের পাশাপাশি, আরেকটি লেআউট ফাইল তৈরি করুন যা কালেকশনের প্রতিটি আইটেমের লেআউটকে সংজ্ঞায়িত করে—যেমন, বইয়ের কালেকশনের প্রতিটি বইয়ের জন্য একটি লেআউট। StackWidget স্যাম্পেলে শুধুমাত্র একটি আইটেম লেআউট ফাইল, widget_item.xml আছে, কারণ সব আইটেম একই লেআউট ব্যবহার করে।

কালেকশন সহ উইজেটের জন্য AppWidgetProvider ক্লাস

সাধারণ উইজেটের মতো, আপনার AppWidgetProvider সাবক্লাসের বেশিরভাগ কোড সাধারণত onUpdate()-এ থাকে। সংগ্রহ সহ উইজেট তৈরি করার সময় onUpdate() আপনার ইমপ্লিমেন্টেশনে প্রধান পার্থক্য হল যে আপনাকে অবশ্যই setRemoteAdapter() কল করতে হবে। এটি কালেকশন ভিউকে বলে যে কোথা থেকে ডেটা পেতে হবে। RemoteViewsService তারপরে আপনার RemoteViewsFactory-এর প্রয়োগ ফেরত দিতে পারে এবং উইজেট উপযুক্ত ডেটা দেখাতে পারে। আপনি এই পদ্ধতি কল করলে, RemoteViewsService-এর আপনার ইমপ্লিমেন্টেশনের দিকে পয়েন্ট করে এমন একটি ইনটেন্ট এবং আপডেট করার জন্য উইজেট নির্দিষ্ট করে এমন উইজেট আইডি পাস করুন।

যেমন, StackWidget স্যাম্পেল কীভাবে onUpdate() কলব্যাক পদ্ধতি প্রয়োগ করে RemoteViewsService-কে উইজেট সংগ্রহের জন্য রিমোট অ্যাডাপ্টর হিসেবে সেট করে তা এখানে দেখানো হল:

override fun onUpdate(
    context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray
) {
    // Update each of the widgets with the remote adapter.
    appWidgetIds.forEach { appWidgetId ->

        // Set up the intent that starts the StackViewService, which
        // provides the views for this collection.
        val intent = Intent(context, StackWidgetService::class.java).apply {
            // Add the widget ID to the intent extras.
            putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
            data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
        }
        // Instantiate the RemoteViews object for the widget layout.
        val views = RemoteViews(context.packageName, R.layout.widget_layout).apply {
            // Set up the RemoteViews object to use a RemoteViews adapter.
            // This adapter connects to a RemoteViewsService through the
            // specified intent.
            // This is how you populate the data.
            setRemoteAdapter(R.id.stack_view, intent)

            // The empty view is displayed when the collection has no items.
            // It must be in the same layout used to instantiate the
            // RemoteViews object.
            setEmptyView(R.id.stack_view, R.id.empty_view)
        }

        // Do additional processing specific to this widget.

        appWidgetManager.updateAppWidget(appWidgetId, views)
    }
    super.onUpdate(context, appWidgetManager, appWidgetIds)
}

ডেটা সেভ করা

এই পৃষ্ঠায় যেভাবে বর্ণনা করা হয়েছে, সেই অনুযায়ী RemoteViewsService সাবক্লাস রিমোট কালেকশন ভিউ পপুলেট করার জন্য ব্যবহৃত RemoteViewsFactory প্রদান করে।

বিশেষত, এইসব ধাপ অনুসরণ করুন:

  1. সাবক্লাস RemoteViewsService. RemoteViewsService হল সেই পরিষেবা যার মাধ্যমে একটি রিমোট অ্যাডাপ্টর RemoteViews অনুরোধ করতে পারে।

  2. আপনার RemoteViewsService সাবক্লাসে, এমন একটি ক্লাস অন্তর্ভুক্ত করুন যা RemoteViewsFactory ইন্টারফেস প্রয়োগ করে। RemoteViewsFactory হল একটি ইন্টারফেস যা ListView, GridView, StackView-এর মতো রিমোট কালেকশন ভিউ এবং সেই ভিউয়ের অন্তর্নিহিত ডেটার মধ্যে অ্যাডাপ্টারের কাজ করে। আপনার ইমপ্লিমেন্টেশন ডেটাসেটের প্রতিটি আইটেমের জন্য RemoteViews অবজেক্ট তৈরি করার জন্য দায়ী। এই ইন্টারফেসটি Adapter-এর চারপাশে একটি পাতলা র‍্যাপার।

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

RemoteViewsService প্রয়োগের মূল কন্টেন্ট হল এর RemoteViewsFactory, যা নিম্নলিখিত বিভাগে বর্ণনা করা হয়েছে।

RemoteViewsFactory ইন্টারফেস

আপনার কাস্টম ক্লাস যা RemoteViewsFactory ইন্টারফেস প্রয়োগ করে, সেটি উইজেটকে তার সংগ্রহে থাকা আইটেমের ডেটা প্রদান করে। এটি করতে, এটি ডেটার সোর্সের সাথে আপনার উইজেট আইটেম XML লেআউট ফাইলকে একত্রিত করে। ডেটার এই সোর্স ডেটাবেস থেকে অ্যারে পর্যন্ত যেকোনও কিছু হতে পারে। StackWidget নমুনাতে, ডেটা সোর্স হল WidgetItems-এর একটি অ্যারে। RemoteViewsFactory ফাংশনটি রিমোট কালেকশন ভিউতে ডেটা যোগ করার জন্য অ্যাডাপ্টর হিসেবে কাজ করে।

আপনার RemoteViewsFactory সাবক্লাসের জন্য আপনাকে যে দুটি সবচেয়ে গুরুত্বপূর্ণ পদ্ধতি প্রয়োগ করতে হবে সেগুলি হল onCreate() ও getViewAt()।

প্রথমবার আপনার ফ্যাক্টরি তৈরি করার সময় সিস্টেম onCreate() কল করে। আপনার ডেটা সোর্সে কোনও কানেকশন বা কার্সর সেট-আপ করার জায়গা এটি। যেমন, StackWidget স্যাম্পেল WidgetItem অবজেক্টের একটি অ্যারে ইনিশিয়ালাইজ করতে onCreate() ব্যবহার করে। আপনার উইজেট অ্যাক্টিভ থাকলে, সিস্টেম এইসব অবজেক্টের অ্যারেতে তাদের ইন্ডেক্স পজিশন ব্যবহার করে অ্যাক্সেস করে এবং সেগুলিতে থাকা টেক্সট দেখায়।

StackWidget স্যাম্পেলের RemoteViewsFactory প্রয়োগ থেকে নেওয়া অংশ এখানে দেওয়া হল, যা onCreate() পদ্ধতির অংশ দেখায়:

private const val REMOTE_VIEW_COUNT: Int = 10

class StackRemoteViewsFactory(
    private val context: Context
) : RemoteViewsService.RemoteViewsFactory {

    private lateinit var widgetItems: List<WidgetItem>

    override fun onCreate() {
        // In onCreate(), set up any connections or cursors to your data
        // source. Heavy lifting, such as downloading or creating content,
        // must be deferred to onDataSetChanged() or getViewAt(). Taking
        // more than 20 seconds on this call results in an ANR.
        widgetItems = List(REMOTE_VIEW_COUNT) { index -> WidgetItem("$index!") }
    }

}

RemoteViewsFactory পদ্ধতি getViewAt() ডেটাসেটের নির্দিষ্ট position-এ থাকা ডেটার সাথে সম্পর্কিত RemoteViews অবজেক্ট রিটার্ন করে। StackWidget স্যাম্পেলের RemoteViewsFactory প্রয়োগ থেকে এখানে একটি অংশ দেওয়া হল:

override fun getViewAt(position: Int): RemoteViews {
    // Construct a remote views item based on the widget item XML file
    // and set the text based on the position.
    return RemoteViews(context.packageName, R.layout.widget_item).apply {
        setTextViewText(R.id.widget_item, widgetItems[position].text)
    }
}

আলাদা আলাদা আইটেমে আচরণ যোগ করা

আগের বিভাগে আপনার উইজেট সংগ্রহের সাথে ডেটা কীভাবে বাইন্ড করবেন তা দেখানো হয়েছে। কিন্তু আপনার কালেকশন ভিউতে থাকা স্বতন্ত্র আইটেমে ডায়নামিক আচরণ যোগ করতে চাইলে কী করবেন?

onUpdate() ক্লাসের মাধ্যমে ইভেন্ট হ্যান্ডেল করা নিবন্ধে যেভাবে বর্ণনা করা হয়েছে, সেই অনুযায়ী আপনি সাধারণত কোনও অবজেক্টের ক্লিক আচরণ সেট করতে setOnClickPendingIntent() ব্যবহার করেন—যেমন, কোনও বোতামে ক্লিক করলে Activity লঞ্চ করা। তবে, কোনও স্বতন্ত্র সংগ্রহ আইটেমের চাইল্ড ভিউয়ের ক্ষেত্রে এই পদ্ধতি ব্যবহার করা যায় না। যেমন, আপনি Gmail উইজেটে অ্যাপ লঞ্চ করার জন্য setOnClickPendingIntent() ব্যবহার করে একটি গ্লোবাল বোতাম সেট-আপ করতে পারেন কিন্তু স্বতন্ত্র তালিকা আইটেমে নয়।

পরিবর্তে, কোনও সংগ্রহের স্বতন্ত্র আইটেমে ক্লিক আচরণ যোগ করতে, setOnClickFillInIntent() ব্যবহার করুন। আপনার কালেকশন ভিউয়ের জন্য একটি পেন্ডিং ইনটেন্ট টেমপ্লেট সেট-আপ করুন এবং আপনার RemoteViewsFactory ব্যবহার করে কালেকশনের প্রতিটি আইটেমে একটি ফিল-ইন ইনটেন্ট সেট করুন।

এই বিভাগে, কীভাবে আলাদা আলাদা আইটেমে আচরণ যোগ করতে হয় তা বোঝাতে StackWidget নমুনা ব্যবহার করা হয়েছে। StackWidget স্যাম্পেলে, ব্যবহারকারী সবচেয়ে উপরের ভিউ স্পর্শ করলে, উইজেট Toast মেসেজ দেখায়,"স্পর্শ করা ভিউ n" যেখানে n হল স্পর্শ করা ভিউয়ের ইনডেক্স (পজিশন)। এটি এইভাবে কাজ করে:

  • StackWidgetProvider—একটি AppWidgetProvider সাবক্লাস—TOAST_ACTION নামের একটি কাস্টম অ্যাকশন সহ একটি পেন্ডিং ইনটেন্ট তৈরি করে।

  • ব্যবহারকারী কোনও ভিউ টাচ করলে, ইনটেন্ট ফায়ার হয় এবং এটি TOAST_ACTION ব্রডকাস্ট করে।

  • এই ব্রডকাস্টটি StackWidgetProvider ক্লাস onReceive()-এর মাধ্যমে ইন্টারসেপ্ট করা হয় এবং উইজেটটি টাচ করা ভিউয়ের জন্য Toast মেসেজ দেখায়। RemoteViewsService-এর মাধ্যমে RemoteViewsFactory-এর দ্বারা সংগ্রহ করা আইটেমের ডেটা প্রদান করা হয়।

বাকি থাকা ইনটেন্ট টেমপ্লেট সেট-আপ করা

StackWidgetProvider (AppWidgetProvider সাবক্লাস) একটি পেন্ডিং ইনটেন্ট সেট-আপ করে। কোনও কালেকশনের স্বতন্ত্র আইটেম নিজস্ব পেন্ডিং ইনটেন্ট সেট-আপ করতে পারে না। এর পরিবর্তে, সামগ্রিকভাবে কালেকশন একটি মুলতুবি থাকা ইনটেন্ট টেমপ্লেট সেট-আপ করে এবং স্বতন্ত্র আইটেম, আইটেম-ভিত্তিক অনন্য ব্যবহার তৈরি করতে একটি ফিল-ইন ইনটেন্ট সেট করে।

এছাড়াও, ব্যবহারকারী কোনও ভিউ টাচ করলে যে ব্রডকাস্ট পাঠানো হয়, এই ক্লাস সেটিও পায়। এটি নিজের onReceive() পদ্ধতিতে এই ইভেন্ট প্রসেস করে। ইনটেন্টের অ্যাকশন TOAST_ACTION হলে, উইজেট বর্তমান ভিউয়ের জন্য Toast মেসেজ দেখায়।

const val TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION"
const val EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM"

class StackWidgetProvider : AppWidgetProvider() {

    // ...

    // Called when the BroadcastReceiver receives an Intent broadcast.
    // Checks whether the intent's action is TOAST_ACTION. If it is, the
    // widget displays a Toast message for the current item.
    override fun onReceive(context: Context, intent: Intent) {
        val mgr: AppWidgetManager = AppWidgetManager.getInstance(context)
        if (intent.action == TOAST_ACTION) {
            val appWidgetId: Int = intent.getIntExtra(
                AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID
            )
            // EXTRA_ITEM represents a custom value provided by the Intent
            // passed to the setOnClickFillInIntent() method to indicate the
            // position of the clicked item. See StackRemoteViewsFactory in
            // Set the fill-in Intent for details.
            val viewIndex: Int = intent.getIntExtra(EXTRA_ITEM, 0)
            Toast.makeText(context, "Touched view $viewIndex", Toast.LENGTH_SHORT).show()
        }
        super.onReceive(context, intent)
    }

    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        // Update each of the widgets with the remote adapter.
        appWidgetIds.forEach { appWidgetId ->

            // Sets up the intent that points to the StackViewService that
            // provides the views for this collection.
            val intent = Intent(context, StackWidgetService::class.java).apply {
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                // When intents are compared, the extras are ignored, so embed
                // the extra sinto the data so that the extras are not ignored.
                data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
            }
            val rv = RemoteViews(context.packageName, R.layout.widget_layout).apply {
                setRemoteAdapter(R.id.stack_view, intent)

                // The empty view is displayed when the collection has no items.
                // It must be a sibling of the collection view.
                setEmptyView(R.id.stack_view, R.id.empty_view)
            }

            // This section makes it possible for items to have individualized
            // behavior. It does this by setting up a pending intent template.
            // Individuals items of a collection can't set up their own pending
            // intents. Instead, the collection as a whole sets up a pending
            // intent template, and the individual items set a fillInIntent
            // to create unique behavior on an item-by-item basis.
            val toastPendingIntent: PendingIntent = Intent(
                context,
                StackWidgetProvider::class.java
            ).run {
                // Set the action for the intent.
                // When the user touches a particular view, it has the effect of
                // broadcasting TOAST_ACTION.
                action = TOAST_ACTION
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))

                // The template must be mutable, because each item fills in its
                // own extras through setOnClickFillInIntent().
                PendingIntent.getBroadcast(
                    context, 0, this,
                    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
                )
            }
            rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent)

            appWidgetManager.updateAppWidget(appWidgetId, rv)
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds)
    }
}

ফিল-ইন ইনটেন্ট সেট করা

আপনার RemoteViewsFactory-কে কালেকশনের প্রতিটি আইটেমে ফিল-ইন ইনটেন্ট সেট করতে হবে। এর ফলে, কোনও আইটেমের স্বতন্ত্র অন-ক্লিক অ্যাকশনকে আলাদা করে চিনে নেওয়া সম্ভব হয়। তারপরে, আইটেমে ট্যাপ করা হলে যে চূড়ান্ত ইনটেন্ট এক্সিকিউট করা হয় তা নির্ধারণ করতে, PendingIntent টেমপ্লেটের সাথে ফিল-ইন ইনটেন্টকে একত্রিত করা হয়।

private const val REMOTE_VIEW_COUNT: Int = 10

class StackRemoteViewsFactory(
    private val context: Context,
    intent: Intent
) : RemoteViewsService.RemoteViewsFactory {

    private lateinit var widgetItems: List<WidgetItem>
    private val appWidgetId: Int = intent.getIntExtra(
        AppWidgetManager.EXTRA_APPWIDGET_ID,
        AppWidgetManager.INVALID_APPWIDGET_ID
    )

    override fun onCreate() {
        // In onCreate(), set up any connections or cursors to your data source.
        // Heavy lifting, such as downloading or creating content, must be
        // deferred to onDataSetChanged() or getViewAt(). Taking more than 20
        // seconds on this call results in an ANR.
        widgetItems = List(REMOTE_VIEW_COUNT) { index -> WidgetItem("$index!") }
        // ...
    }

    // ...

    override fun getViewAt(position: Int): RemoteViews {
        // Construct a remote views item based on the widget item XML file
        // and set the text based on the position.
        return RemoteViews(context.packageName, R.layout.widget_item).apply {
            setTextViewText(R.id.widget_item, widgetItems[position].text)

            // Set a fill-intent to fill in the pending intent template.
            // that is set on the collection view in StackWidgetProvider.
            val fillInIntent = Intent().apply {
                Bundle().also { extras ->
                    extras.putInt(EXTRA_ITEM, position)
                    putExtras(extras)
                }
            }
            // Make it possible to distinguish the individual on-click
            // action of a given item.
            setOnClickFillInIntent(R.id.widget_item, fillInIntent)
            // ...
        }
    }
    // ...
}

সংগ্রহ করা ডেটা আপ-টু-ডেট রাখা

যেসব উইজেট কালেকশন ব্যবহার করে, সেগুলির আপডেট ফ্লো ২ নম্বর ছবিতে দেখানো হয়েছে। এটি দেখায় যে কীভাবে উইজেট কোড RemoteViewsFactory-এর সাথে ইন্টার‍্যাক্ট করে এবং কীভাবে আপনি আপডেট ট্রিগার করতে পারেন:

আপডেট চলাকালীন RemoteViewsFactory-এর সাথে ইন্টার‍্যাকশন
ছবি ২. আপডেট চলাকালীন RemoteViewsFactory-এর সাথে ইন্টার‍্যাকশন।

যেসব উইজেট সংগ্রহ ব্যবহার করে সেগুলি ব্যবহারকারীদের আপ-টু-ডেট কন্টেন্ট প্রদান করতে পারে। যেমন, Gmail উইজেট ব্যবহারকারীদের ইনবক্সের একটি স্ন্যাপশট দেয়। এটি সম্ভব করতে, নতুন ডেটা আনতে ও দেখাতে আপনার RemoteViewsFactory ও সংগ্রহ ভিউ ট্রিগার করুন।

এটি করতে, AppWidgetManager ব্যবহার করে notifyAppWidgetViewDataChanged()-কে কল করুন। এই কলের ফলে আপনার RemoteViewsFactory অবজেক্টের onDataSetChanged() মেথডে কলব্যাক করা হয়, যা আপনাকে যেকোনও নতুন ডেটা ফেচ করতে দেয়।

আপনি onDataSetChanged() কলব্যাকের মধ্যে সিঙ্ক্রোনাসভাবে প্রসেসিং-ইনটেনসিভ অপারেশন পারফর্ম করতে পারবেন। RemoteViewsFactory থেকে মেটাডেটা বা ভিউ ডেটা ফেচ করার আগেই এই কলটি সম্পূর্ণ হয়ে যায়। এছাড়াও, আপনি getViewAt() মেথডের মধ্যে প্রসেসিং-ইনটেনসিভ অপারেশন পারফর্ম করতে পারবেন। এই কলটি আসতে দেরি হলে, RemoteViewsFactory অবজেক্টের getLoadingView() মেথড—এর মাধ্যমে নির্দিষ্ট করা লোডিং ভিউ—এটি রিটার্ন না করা পর্যন্ত কালেকশন ভিউয়ের অনুরূপ পজিশনে দেখানো হয়।

সরাসরি কোনও কালেকশন পাস করতে RemoteCollectionItems ব্যবহার করুন

Android 12 (API লেভেল 31) setRemoteAdapter(int viewId, RemoteViews.RemoteCollectionItems items) মেথড যোগ করে, যা আপনার অ্যাপকে কালেকশন ভিউ পূরণ করার সময় সরাসরি একটি কালেকশন পাস করতে দেয়। আপনি এই পদ্ধতি ব্যবহার করে অ্যাডাপ্টার সেট করলে, আপনাকে RemoteViewsFactory ইমপ্লিমেন্ট করতে হবে না এবং notifyAppWidgetViewDataChanged() কল করতে হবে না।

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

সংগ্রহে যদি লেআউটের একটি নির্দিষ্ট সেট ব্যবহার করা না হয়—অর্থাৎ, যদি কিছু আইটেম শুধুমাত্র কখনও কখনও উপস্থিত থাকে—তাহলে সংগ্রহের মধ্যে থাকতে পারে এমন অনন্য লেআউটের সর্বাধিক সংখ্যা নির্দিষ্ট করতে setViewTypeCount ব্যবহার করুন। এর ফলে আপনার অ্যাপ উইজেট আপডেট করার সময় অ্যাডাপ্টারটি আবার ব্যবহার করা যায়।

সহজ RemoteViews সংগ্রহ কীভাবে প্রয়োগ করতে হয় তার একটি উদাহরণ এখানে দেওয়া হল।

val itemLayouts = listOf(
    R.layout.item_type_1,
    R.layout.item_type_2,
    // ...
)

remoteView.setRemoteAdapter(
    R.id.list_view,
    RemoteViews.RemoteCollectionItems.Builder()
        .addItem(/* id= */ ID_1, RemoteViews(context.packageName, R.layout.item_type_1))
        .addItem(/* id= */ ID_2, RemoteViews(context.packageName, R.layout.item_type_2))
        // ...
        .setViewTypeCount(itemLayouts.count())
        .build()
)