שימוש בווידג'טים של אוספים

איך עושים את זה ב-Compose
‫Jetpack Compose היא ערכת הכלים המומלצת לפיתוח ממשק משתמש ב-Android. איך יוצרים ווידג'טים באמצעות ממשקי API בסגנון Compose.

ווידג'טים של אוספים מתמחים בהצגת רכיבים רבים מאותו סוג, כמו אוספי תמונות מאפליקציית גלריה, מאמרים מאפליקציית חדשות או הודעות מאפליקציית תקשורת. ווידג'טים של אוספים מתמקדים בדרך כלל בשני תרחישי שימוש: עיון באוסף ופתיחת רכיב של האוסף לתצוגת הפרטים שלו. אפשר לגלול אנכית בווידג'טים של אוספים.

הווידג'טים האלה משתמשים ב-RemoteViewsService כדי להציג אוספים שמגובים בנתונים מרוחקים, כמו מספק לניהול גישה. הווידג'ט מציג את הנתונים באמצעות אחד מסוגי התצוגה הבאים, שנקראים תצוגות של אוספים:

‫ListView: תצוגה שבה הפריטים מופיעים ברשימה עם גלילה אנכית.

‫GridView: תצוגה שבה הפריטים מוצגים ברשת דו-ממדית שאפשר לגלול בה.

StackView: תצוגת כרטיסי מיקום בערימה – כמו כרטסת – שבה המשתמש יכול להחליק את הכרטיס הקדמי למעלה או למטה כדי לראות את הכרטיס הקודם או הבא, בהתאמה.

‫AdapterViewFlipper: רכיב ViewAnimator שמגובה במתאם ויוצר אנימציה בין שתי תצוגות או יותר. בכל פעם מוצג רק ילד אחד.

מכיוון שתצוגות האוספים האלה מציגות אוספים שמגובים בנתונים מרוחקים, הן משתמשות ב-Adapter כדי לקשר את ממשק המשתמש שלהן לנתונים שלהן. Adapter מקשר בין פריטים ספציפיים מקבוצת נתונים לבין אובייקטים ספציפיים של View.

מכיוון שתצוגות האוספים האלה מגובות על ידי מתאמים, מסגרת Android חייבת לכלול ארכיטקטורה נוספת כדי לתמוך בשימוש בהן בווידג'טים. בהקשר של ווידג'ט, Adapter מוחלף ב-RemoteViewsFactory, שהוא wrapper דק סביב הממשק של Adapter. כשמבקשים פריט ספציפי באוסף, הפונקציה RemoteViewsFactory יוצרת ומחזירה את הפריט לאוסף כאובייקט RemoteViews. כדי לכלול תצוגת אוסף בווידג'ט, צריך להטמיע את RemoteViewsService ואת RemoteViewsFactory.

‫RemoteViewsService הוא שירות שמאפשר למתאם מרוחק לבקש אובייקטים מסוג RemoteViews. ‫RemoteViewsFactory הוא ממשק למתאם בין תצוגת אוסף (כמו ListView,‏ GridView ו-StackView) לבין הנתונים הבסיסיים של התצוגה הזו. בדוגמה של StackWidget, זהו קוד שחוזר על עצמו (boilerplate) כדי להטמיע את השירות והממשק האלה:

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 שמציג תצוגות בערימה
איור 1. StackWidget.

הדוגמה הזו מורכבת מערימה של עשר תצוגות שמציגות את הערכים מאפס עד תשע. לווידג'ט לדוגמה יש את ההתנהגויות העיקריות הבאות:

  • המשתמש יכול להחליק במהירות אנכית את מבט המעל בווידג'ט כדי להציג את התצוגה הבאה או הקודמת. זו התנהגות מובנית של StackView.

  • בלי אינטראקציה עם המשתמש, הווידג'ט עובר באופן אוטומטי בין התצוגות שלו ברצף, כמו מצגת. הסיבה לכך היא ההגדרה android:autoAdvanceViewId="@id/stack_view" בקובץ res/xml/stackwidgetinfo.xml. ההגדרה הזו חלה על מזהה התצוגה, שבמקרה הזה הוא מזהה התצוגה של תצוגת הערימה.

  • אם המשתמש נוגע בתצוגה העליונה, הווידג'ט מציג את ההודעה Toast "Touched view 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. הקובץ widget_layout.xml של הדוגמה StackWidget:

<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, והווידג'ט יכול להציג את הנתונים המתאימים. כשקוראים ל-method הזה, מעבירים Intent שמצביע על ההטמעה של RemoteViewsService ומזהה הווידג'ט שמציין את הווידג'ט שרוצים לעדכן.

לדוגמה, כך הדוגמה StackWidget מטמיעה את שיטת הקריאה החוזרת (callback) 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 נעשה שימוש ב-onCreate() כדי לאתחל מערך של אובייקטים מסוג WidgetItem. כשהווידג'ט פעיל, המערכת ניגשת לאובייקטים האלה באמצעות מיקום האינדקס שלהם במערך ומציגה את הטקסט שהם מכילים.

הנה קטע מההטמעה של RemoteViewsFactory בדוגמה של StackWidget, שמציג חלקים מה-method‏ 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!") }
    }

}

ה-method‏ RemoteViewsFactory‏ getViewAt() מחזירה אובייקט RemoteViews שתואם לנתונים ב-position שצוין במערך הנתונים. הנה קטע מההטמעה של RemoteViewsFactory בדוגמה של StackWidget:

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. אבל הגישה הזו לא מותרת לתצוגות צאצא בפריט אוסף נפרד. לדוגמה, אפשר להשתמש ב-setOnClickPendingIntent() כדי להגדיר לחצן גלובלי בווידג'ט של Gmail שמפעיל את האפליקציה, אבל לא בפריטים ספציפיים ברשימה.

במקום זאת, כדי להוסיף התנהגות קליק לפריטים ספציפיים באוסף, משתמשים ב-setOnClickFillInIntent(). מגדירים תבנית של Intent בהמתנה לתצוגת האוסף, ומגדירים Intent למילוי בכל פריט באוסף באמצעות RemoteViewsFactory.

בקטע הזה נשתמש בדוגמה של StackWidget כדי להסביר איך להוסיף התנהגות לפריטים ספציפיים. בדוגמה של StackWidget, אם המשתמש נוגע בתצוגה העליונה, הווידג'ט מציג את ההודעה Toast "Touched view n", כאשר n הוא האינדקס (המיקום) של התצוגה שבה נגעו. כך זה עובד:

  • ה-StackWidgetProvider – מחלקה משנית של AppWidgetProvider – יוצרת intent בהמתנה עם פעולה מותאמת אישית שנקראת TOAST_ACTION.

  • כשהמשתמש נוגע בתצוגה, ה-intent מופעל ומשדר את TOAST_ACTION.

  • השידור הזה נקלט על ידי השיטה (method) onReceive() של המחלקה StackWidgetProvider, והווידג'ט מציג את ההודעה Toast בתצוגה שנלחצה. הנתונים של הפריטים באוסף מסופקים על ידי RemoteViewsFactory באמצעות RemoteViewsService.

הגדרת התבנית של ה-intent בהמתנה

ה-StackWidgetProvider (מחלקה משנית של AppWidgetProvider) מגדיר intent בהמתנה. אי אפשר להגדיר Intent בהמתנה לפריטים ספציפיים באוסף. במקום זאת, האוסף כולו מגדיר תבנית של Intent בהמתנה, והפריטים הבודדים מגדירים Intent למילוי כדי ליצור התנהגות ייחודית לכל פריט בנפרד.

המחלקה הזו מקבלת גם את השידור שנשלח כשהמשתמש נוגע בתצוגה. הוא מעבד את האירוע הזה ב-method ‏onReceive() שלו. אם הפעולה של ה-Intent היא 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 צריך להגדיר כוונה למילוי בכל פריט באוסף. כך אפשר להבחין בין פעולות הלחיצה השונות של פריט מסוים. לאחר מכן, ה-Intent למילוי משולב עם התבנית PendingIntent כדי לקבוע את ה-Intent הסופי שמופעל כשמקישים על הפריט.

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)
            // ...
        }
    }
    // ...
}

שמירה על עדכניות הנתונים של הקולקציה

איור 2 ממחיש את תהליך העדכון בווידג'ט שמשתמש באוספים. הוא מראה איך הקוד של הווידג'ט יוצר אינטראקציה עם RemoteViewsFactory ואיך אפשר להפעיל עדכונים:

אינטראקציה עם RemoteViewsFactory במהלך עדכונים
איור 2. אינטראקציה עם RemoteViewsFactory במהלך עדכונים.

ווידג'טים שמשתמשים באוספים יכולים לספק למשתמשים תוכן עדכני. לדוגמה, הווידג'ט של Gmail נותן למשתמשים תמונת מצב של תיבת הדואר הנכנס שלהם. כדי לעשות את זה, צריך להפעיל את RemoteViewsFactory ואת תצוגת האוסף כדי לאחזר ולהציג נתונים חדשים.

כדי לעשות את זה, משתמשים ב-AppWidgetManager כדי להפעיל את notifyAppWidgetViewDataChanged(). הקריאה הזו גורמת לקריאה חוזרת (callback) ל-method‏ onDataSetChanged() של האובייקט RemoteViewsFactory, שמאפשרת לאחזר נתונים חדשים.

אפשר לבצע פעולות שדורשות עיבוד אינטנסיבי באופן סינכרוני בתוך פונקציית ה-callback של onDataSetChanged(). הקריאה הזו מסתיימת לפני שהמטא-נתונים או נתוני התצוגה נשלפים מ-RemoteViewsFactory. אפשר גם לבצע פעולות שדורשות עיבוד אינטנסיבי בתוך השיטה getViewAt(). אם הקריאה הזו נמשכת זמן רב, תצוגת הטעינה – שצוינה על ידי ה-method‏ getLoadingView() של האובייקט RemoteViewsFactory – מוצגת במיקום המתאים של תצוגת האוסף עד שהיא חוזרת.

שימוש ב-RemoteCollectionItems כדי להעביר קולקציה ישירות

ב-Android 12 (רמת API‏ 31) נוספה השיטה setRemoteAdapter(int viewId, RemoteViews.RemoteCollectionItems items), שמאפשרת לאפליקציה להעביר אוסף ישירות כשהיא מאכלסת תצוגת אוסף. אם מגדירים את המתאם בשיטה הזו, לא צריך להטמיע RemoteViewsFactory ולא צריך להפעיל את notifyAppWidgetViewDataChanged().

בנוסף לכך שקל יותר לאכלס את המתאם, הגישה הזו גם מסירה את זמן האחזור לאכלוס פריטים חדשים כשהמשתמשים גוללים ברשימה כדי לחשוף פריט חדש. הגישה הזו להגדרת המתאם עדיפה כל עוד קבוצת הפריטים באוסף קטנה יחסית. עם זאת, הגישה הזו לא פועלת היטב אם האוסף מכיל מספר רב של Bitmaps שמועברים אל setImageViewBitmap.

אם האוסף לא משתמש בקבוצה קבועה של פריסות, כלומר אם פריטים מסוימים מופיעים רק לפעמים, משתמשים ב-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()
)