Skip to main content

Working with App Widgets

1. Creating Widget Provider Class

Property Description Values
AppWidgetProvider Base class for implementing app widgets android.appwidget.AppWidgetProvider
onUpdate Called when widget is updated or first created override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray)
onEnabled Called when the first widget is created override fun onEnabled(context: Context)
onDisabled Called when the last widget is deleted override fun onDisabled(context: Context)

Example: Basic Widget Provider Implementation

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews

class MyWidgetProvider : AppWidgetProvider() {
    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        for (appWidgetId in appWidgetIds) {
            val views = RemoteViews(context.packageName, R.layout.widget_layout)
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }

    override fun onEnabled(context: Context) {
        // When the first widget is created
    }

    override fun onDisabled(context: Context) {
        // When the last widget is deleted
    }
}
Note: The AppWidgetProvider is a subclass of BroadcastReceiver, so it can receive broadcasts like android.appwidget.action.APPWIDGET_UPDATE.

2. Defining Widget Layout

Property Description Values
appWidgetProvider Defines the widget's behavior and updates android.appwidget.AppWidgetProvider
minWidth Minimum width of the widget in dp 180dp
minHeight Minimum height of the widget in dp 40dp
updatePeriodMillis How often the widget updates (in milliseconds) 30000

Example: Basic Widget Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/widget_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello Widget" />

</LinearLayout>
Note: The layout must be defined in res/layout/ directory and referenced in the AppWidgetProviderInfo XML.

3. Configuring Widget Metadata

Field Description Value
appWidgetProvider Class that extends AppWidgetProvider com.example.MyWidgetProvider
label User-facing name of the widget @string/widget_label
initialLayout Layout resource ID for widget UI @layout/widget_layout
minWidth Minimum width in dp 250dp
minHeight Minimum height in dp 40dp
updatePeriodMillis Update interval in milliseconds (0 = manual) 30000
previewImage Preview image for widget in launcher @drawable/widget_preview
resizeMode Allowed resize behavior (none, horizontal, vertical, all) AppWidgetManager.RESIZE_HORIZONTAL

Example: Widget Metadata in XML

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:appWidgetProvider="com.example.MyWidgetProvider"
    android:label="@string/widget_label"
    android:initialLayout="@layout/widget_layout"
    android:minWidth="250dp"
    android:minHeight="40dp"
    android:updatePeriodMillis="30000"
    android:previewImage="@drawable/widget_preview"
    android:resizeMode="horizontal" />

4. Updating Widget Content

Method Description Notes
AppWidgetManager.updateAppWidget() Updates the widget UI with new data Must be called from a BroadcastReceiver or Service
RemoteViews.setImageViewResource() Updates an image view with a new resource Use for dynamic image updates (e.g., loading from network)
RemoteViews.setTextViewText() Updates a text view with new content Supports dynamic string updates
RemoteViews.setImageViewUri() Updates an image view with a URI (e.g., from content provider) Useful for loading images from external sources

Example: Updating Widget Text

val remoteViews = RemoteViews(context.packageName, R.layout.widget_layout)
remoteViews.setTextViewText(R.id.widget_text, "Updated Text")
val appWidgetManager = AppWidgetManager.getInstance(context)
appWidgetManager.updateAppWidget(widgetId, remoteViews)

Example: Updating Widget Image

val remoteViews = RemoteViews(context.packageName, R.layout.widget_layout)
remoteViews.setImageViewResource(R.id.widget_image, R.drawable.new_image)
val appWidgetManager = AppWidgetManager.getInstance(context)
appWidgetManager.updateAppWidget(widgetId, remoteViews)
Note: The AppWidgetManager.updateAppWidget() method must be called from a background thread or service to avoid blocking the UI.
Warning: Updating widgets too frequently can lead to battery drain and performance issues. Consider using AlarmManager for periodic updates.

5. Handling Widget Clicks

Click Type Description Implementation
Button Click Triggers when user taps a button inside the widget
val clickIntent = Intent(context, MyWidgetReceiver::class.java).apply {
    action = "com.example.WIDGET_BUTTON_CLICKED"
    putExtra("widgetId", appWidgetId)
}
val pendingIntent = PendingIntent.getBroadcast(context, 0, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent(R.id.widget_button, pendingIntent)
App Launch Opens the main app when widget is tapped
val launchIntent = Intent(context, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent(R.id.widget_container, pendingIntent)

Example: Broadcast Receiver for Widget Clicks

class MyWidgetReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if (intent?.action == "com.example.WIDGET_BUTTON_CLICKED") {
            val widgetId = intent.getIntExtra("widgetId", 0)
            // Handle button click logic here
        }
    }
}
Note: Ensure the broadcast receiver is declared in AndroidManifest.xml

6. Implementing Widget Configuration

Example: Basic AppWidgetProvider

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews

class MyWidgetProvider : AppWidgetProvider() {
    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        for (appWidgetId in appWidgetIds) {
            val views = RemoteViews(context.packageName, R.layout.widget_layout)
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }
}
Property Description Values
AppWidgetProvider Base class for widget providers Extends BroadcastReceiver
onUpdate Called when widget is updated Override to update widget content
RemoteViews Defines widget UI layout Uses layout resource ID
Note: Widget configuration is handled via a separate activity that can be launched from the widget.

Example: Configuration Activity

import android.app.Activity
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.os.Bundle

class WidgetConfigActivity : Activity() {
    private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.config_layout)

        val intent = intent
        val extras = intent.extras

        if (extras != null) {
            appWidgetId = extras.getInt(
                AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID
            )
        }

        val resultValue = Intent()
        resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
        setResult(Activity.RESULT_OK, resultValue)
        finish()
    }
}

7. Using Collection Widgets

Collection widgets in Android allow you to display a dynamic list of items, such as a list of notifications or a grid of images. These widgets are particularly useful for showing content that changes frequently.

Property Description Values
appWidgetProvider Class that handles widget updates and lifecycle events. AppWidgetProvider
collectionItems Array of items to display in the collection widget. Array<WidgetItem>
layoutResource Resource ID of the layout to use for each item in the collection. R.layout.widget_item

Example: Creating a Collection Widget

class MyCollectionWidget : AppWidgetProvider() {
    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        val widgets = getCollectionWidgets(context)
        for (id in appWidgetIds) {
            val intent = Intent(context, MyCollectionService::class.java)
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
            val pendingIntent = PendingIntent.getService(
                context,
                id,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )
            val remoteViews = RemoteViews(context.packageName, R.layout.widget_collection)
            remoteViews.setRemoteAdapter(R.id.listView, pendingIntent)
            appWidgetManager.updateAppWidget(id, remoteViews)
        }
    }

    private fun getCollectionWidgets(context: Context): List<WidgetItem> {
        return listOf(
            WidgetItem("Item 1", "Description 1"),
            WidgetItem("Item 2", "Description 2")
        )
    }
}

Example: Service for Collection Items

class MyCollectionService : RemoteViewsService() {
    override fun onGetViewFactory(intent: Intent): RemoteViewsService.RemoteViewsFactory {
        return MyCollectionFactory(intent)
    }

    class MyCollectionFactory(private val intent: Intent) : RemoteViewsService.RemoteViewsFactory {
        private var items = mutableListOf<WidgetItem>()

        override fun onCreate() {
            items = getCollectionItems()
        }

        override fun onDataSetChanged() {
            items = getCollectionItems()
        }

        override fun onDestroy() {}

        override fun getCount(): Int = items.size

        override fun getViewAt(position: Int): RemoteViews {
            val views = RemoteViews(context.packageName, R.layout.widget_item)
            views.setTextViewText(R.id.title, items[position].title)
            views.setTextViewText(R.id.description, items[position].description)
            return views
        }

        override fun getLoadingView(): RemoteViews? = null

        override fun getViewTypeCount(): Int = 1

        override fun getItemId(position: Int): Long = position.toLong()

        override fun hasStableIds(): Boolean = true

        private fun getCollectionItems(): MutableList<WidgetItem> {
            return mutableListOf(
                WidgetItem("Item 1", "Description 1"),
                WidgetItem("Item 2", "Description 2")
            )
        }
    }
}
Note: Collection widgets require a RemoteViewsService to provide the content dynamically.
Warning: Ensure that your collection widget updates efficiently to avoid performance issues.

Key Points

  • Collection widgets are ideal for displaying lists of items.
  • They require a RemoteViewsService to populate the list dynamically.
  • Each item in the collection must have a corresponding layout resource.

8. Creating RemoteViewsService

A RemoteViewsService is a special type of service that provides data to an AppWidgetProvider for displaying dynamic content in a widget. It is particularly useful when you need to populate list views or other dynamic content within your app widget.

Example: Basic RemoteViewsService Implementation

import android.app.Service
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.os.IBinder
import android.widget.RemoteViewsService

class MyRemoteViewsService : RemoteViewsService() {
    override fun onBind(intent: Intent): IBinder {
        return RemoteViewsService.RemoteViewsFactory()
    }
}

The RemoteViewsService must implement the RemoteViewsService.RemoteViewsFactory interface to provide data for the widget. This factory is responsible for creating and managing the RemoteViews that will be displayed in the widget.

Note: The RemoteViewsService runs in a separate process, so it's important to ensure that any data you pass between the service and the widget provider is properly serialized.
Method Description Return Type
onCreate() Called once when the service is created. Initialize any resources here. void
onDataSetChanged() Called when the data set changes. This is where you update your data source and notify the widget. void
onDestroy() Called when the service is destroyed. Clean up any resources here. void
getCount() Returns the number of items in the data set. Int
getViewAt(position: Int) Returns the RemoteViews for a specific item at the given position. RemoteViews
getLoadingView() Returns a RemoteViews to display while the data is loading. RemoteViews
getViewTypeCount() Returns the number of view types supported by this factory. Int
getItemId(position: Int) Returns the ID of the item at the given position. Long
hasStableIds() Indicates whether the IDs of items are stable across data changes. Boolean

Example: Implementing RemoteViewsFactory

class MyRemoteViewsFactory : RemoteViewsService.RemoteViewsFactory {
    private var data: List<String> = emptyList()

    override fun onCreate() {
        // Initialize data
        data = listOf("Item 1", "Item 2", "Item 3")
    }

    override fun onDataSetChanged() {
        // Update data if needed
    }

    override fun onDestroy() {
        // Clean up resources
    }

    override fun getCount(): Int = data.size

    override fun getViewAt(position: Int): RemoteViews {
        val views = RemoteViews(context.packageName, R.layout.widget_item)
        views.setTextViewText(R.id.text_view, data[position])
        return views
    }

    override fun getLoadingView(): RemoteViews? = null

    override fun getViewTypeCount(): Int = 1

    override fun getItemId(position: Int): Long = position.toLong()

    override fun hasStableIds(): Boolean = true
}

9. Managing Widget Lifecycle

App Widgets in Android have a distinct lifecycle that developers must manage to ensure proper behavior and resource usage. The lifecycle includes creation, updates, and destruction phases.

Phase Description Key Methods
Creation Initial setup of the widget, including configuration and binding. onUpdate(), onAppWidgetOptionsChanged()
Update Periodic or triggered updates of the widget's UI. onUpdate(), onReceive()
Deletion When the widget is removed from the home screen. onDeleted(), onDisabled()

Example: Handling Widget Updates

class MyWidgetReceiver : AppWidgetProvider() {
    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        for (appWidgetId in appWidgetIds) {
            val remoteViews = RemoteViews(context.packageName, R.layout.widget_layout)
            // Update widget content
            appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
        }
    }
}
Note: The onUpdate() method is called when the widget is initially added or when the update interval specified in the configuration passes.
Warning: Avoid heavy operations in onUpdate() as it runs on the UI thread and can block the home screen.

Widget Lifecycle Summary

  • onUpdate(): Called when the widget is added or updated.
  • onDeleted(): Called when widgets are deleted from the home screen.
  • onDisabled(): Called when the last instance of the widget is removed.
  • onAppWidgetOptionsChanged(): Called when the widget options change (e.g., size).

10. Updating Widgets from App

Widgets can be updated from an app using AppWidgetManager and RemoteViews. Updates can be triggered manually or via broadcasts.

Method Description Usage
updateAppWidget() Updates a specific widget instance AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, views)
notifyAppWidgetViewDataChanged() Notifies widget that data has changed AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, viewId)

Example: Updating Widget Text

val appWidgetManager = AppWidgetManager.getInstance(context)
val views = RemoteViews(context.packageName, R.layout.widget_layout)
views.setTextViewText(R.id.widget_text, "Updated Text")
appWidgetManager.updateAppWidget(appWidgetId, views)

Example: Using Broadcast to Update Widget

val intent = Intent(context, WidgetUpdateReceiver::class.java)
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent(R.id.update_button, pendingIntent)

11. Using Glance for Compose Widgets

The Glance library is a lightweight framework for building Android app widgets using Jetpack Compose. It provides a declarative API to define widget layouts and logic, similar to how you would build UIs in a regular Compose application.

Feature Description Notes
GlanceAppWidget Base class for creating app widgets using Compose. Extends AppWidget and uses GlanceAppWidgetReceiver.
GlanceAppWidgetReceiver Receiver that handles widget updates and lifecycle events. Must be declared in AndroidManifest.xml.
RemoteViews Used to define the layout and behavior of widgets. Supports Compose-based layouts via GlanceAppWidget.

Example: Basic Glance Widget

class MyGlanceWidget : GlanceAppWidget() {
    override suspend fun provideGlance(context: Context, id: AppWidgetId) = GlanceState(
        appWidgetId = id,
        layout = MyWidgetLayout
    )
}

@Composable
fun MyWidgetLayout() {
    Box(
        modifier = GlanceModifier.fillMaxSize()
            .background(Color.Blue)
    ) {
        Text(
            text = "Hello, Widget!",
            modifier = GlanceModifier.fillMaxSize().padding(16.dp),
            color = Color.White,
            fontSize = 18.sp
        )
    }
}
Note: Glance widgets are not supported on all Android versions. Ensure compatibility with the target API level.
Warning: Glance widgets do not support all Jetpack Compose features. Some advanced composables may not render correctly.

Key Features of Glance

  • Declarative UI: Build widgets using Jetpack Compose syntax.
  • State Management: Use GlanceState to manage widget state.
  • Lifecycle Support: Handles widget updates and lifecycle events.

Was this article helpful?