Skip to content

Creating an App Widget

Mitchell Bryson edited this page Aug 5, 2026 · 3 revisions

Overview

This guide covers how to create an app widget. A widget is a small gadget or control of your android application placed on the home screen. Widgets can be very handy as they allow you to put your favourite applications on your home screen in order to quickly access them. You have probably seen some common widgets, such as music widget, weather widget, clock widget e.t.c

Widgets could be of many types such as information widgets, collection widgets, control widgets and hybrid widgets. Android provides us a complete framework to develop our own widgets — see the official App widgets overview. This guide covers the classic View-based (RemoteViews) approach; for Compose-style widget development, check out Jetpack Glance.

Basics

To create an App Widget, you need the following:

  • AppWidgetProviderInfo object Describes the metadata for an App Widget, such as the App Widget's layout, update frequency, and the AppWidgetProvider class. This should be defined in XML.
  • AppWidgetProvider class implementation Defines the basic methods that allow you to programmatically interface with the App Widget, based on broadcast events. Through it, you will receive broadcasts when the App Widget is updated, enabled, disabled and deleted.
  • View layout Defines the initial layout for the App Widget, defined in XML. Widget layouts are based on RemoteViews, which supports only a subset of layouts and views — no custom views.

Declaring an App Widget in the Manifest

First, declare the AppWidgetProvider subclass as a <receiver> in your application's AndroidManifest.xml file. Note that apps targeting Android 12 (API level 31) or higher must declare android:exported explicitly on any component that has an <intent-filter>; the system can still deliver the APPWIDGET_UPDATE broadcast with android:exported="false", which is the value the official manifest example uses:

<receiver android:name=".ExampleAppWidgetProvider"
          android:exported="false">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/example_appwidget_info" />
</receiver>

Widget - XML metadata file

The AppWidgetProviderInfo object is defined in a separate XML file under res/xml (create the folder if it doesn't exist). Since Android 12, the default widget size is expressed in home-screen grid cells via targetCellWidth / targetCellHeight; those attributes are ignored on Android 11 and lower, so Google recommends declaring both the cell-based attributes and the older dp-based minWidth / minHeight so older devices can fall back to them. minResizeWidth/minResizeHeight and maxResizeWidth/maxResizeHeight (the latter pair introduced in Android 12) bound how far the user can resize the widget, and previewLayout/description improve how the widget appears in the widget picker. See the widget sizing attributes documentation for the full attribute reference.

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="80dp"
    android:minHeight="80dp"
    android:targetCellWidth="2"
    android:targetCellHeight="2"
    android:minResizeWidth="40dp"
    android:minResizeHeight="40dp"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="250dp"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="86400000"
    android:description="@string/example_appwidget_description"
    android:previewLayout="@layout/example_appwidget"
    android:initialLayout="@layout/example_appwidget"
    android:widgetCategory="home_screen">
</appwidget-provider>

Note that updatePeriodMillis cannot be less than 30 minutes (1800000); 86400000 requests one update per day. A value of 0 disables periodic updates entirely.

Widget - Layout file

Define the widget's layout as its own file under res/layout (don't reuse an activity layout — the widget renders through RemoteViews, which only supports FrameLayout, LinearLayout, RelativeLayout, GridLayout and a fixed set of views such as TextView, Button, ImageView, and list containers). For example, res/layout/example_appwidget.xml:

<?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"
    android:gravity="center">

    <TextView
        android:id="@+id/appwidget_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/appwidget_text" />

    <Button
        android:id="@+id/appwidget_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/appwidget_button" />

</LinearLayout>

Widget - Provider class

Create a class that extends AppWidgetProvider (a specialized BroadcastReceiver — it is not an Activity, so don't mix it into your MainActivity) and override onUpdate(). This example makes the widget's button open a web page when tapped:

class ExampleAppWidgetProvider : AppWidgetProvider() {

    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        // Perform this loop for each widget instance that belongs to this provider.
        for (appWidgetId in appWidgetIds) {
            // Create an Intent to open a website when the button is tapped.
            val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://codepath.com"))
            val pendingIntent = PendingIntent.getActivity(
                context,
                /* requestCode = */ 0,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )

            // Get the layout for the widget and attach a click listener to the button.
            val views = RemoteViews(context.packageName, R.layout.example_appwidget)
            views.setOnClickPendingIntent(R.id.appwidget_button, pendingIntent)

            // Tell the AppWidgetManager to perform an update on the current widget.
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }
}

Apps targeting Android 12 (API level 31) or higher must specify either PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_MUTABLE when creating a PendingIntent; omitting the flag throws IllegalArgumentException at runtime. Prefer FLAG_IMMUTABLE unless the receiver needs to fill in the intent (e.g., inline replies, bubbles).

Apart from onUpdate(), there are other lifecycle callbacks defined on AppWidgetProvider:

Sr.No Method & Description
1 onDeleted(Context context, int[] appWidgetIds) This is called when an instance of AppWidgetProvider is deleted.
2 onDisabled(Context context) This is called when the last instance of AppWidgetProvider is deleted
3 onEnabled(Context context) This is called when the first instance of AppWidgetProvider is created.
4 onReceive(Context context, Intent intent) It is used to dispatch calls to the various methods of the class

Responsive layouts (Android 12+)

On Android 12 and higher, instead of supplying a single layout, onUpdate() can map exact widget sizes to different RemoteViews so the launcher can swap layouts without waking your app when the user resizes the widget:

override fun onUpdate(
    context: Context,
    appWidgetManager: AppWidgetManager,
    appWidgetIds: IntArray
) {
    for (appWidgetId in appWidgetIds) {
        val smallView: RemoteViews = ...
        val tallView: RemoteViews = ...
        val wideView: RemoteViews = ...

        val viewMapping: Map<SizeF, RemoteViews> = mapOf(
            SizeF(150f, 100f) to smallView,
            SizeF(150f, 200f) to tallView,
            SizeF(215f, 100f) to wideView
        )
        appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(viewMapping))
    }
}

See Provide responsive layouts for the size-mapping rules and the pre-Android-12 fallback (onAppWidgetOptionsChanged()).

Collection widgets

Widgets that display a scrollable list of items (like the Gmail widget) attach an adapter to a ListView, GridView, or StackView in the widget layout. Android 12 (API level 31) added RemoteViews.RemoteCollectionItems and the setRemoteAdapter(int viewId, RemoteViews.RemoteCollectionItems items) overload, which passes the collection directly — with it you don't need to implement a RemoteViewsService/RemoteViewsFactory or call notifyAppWidgetViewDataChanged(), and Google documents it as the preferred approach for reasonably small collections. The older setRemoteAdapter(int, Intent) overload that binds to a RemoteViewsService is deprecated in favor of it, and remains only for large or slow-to-load data sets on older platform versions. On earlier API levels, RemoteViewsCompat.setRemoteAdapter() from androidx.core:core-remoteviews backports the direct-collection approach. See Use collections with widgets for complete examples.

Stateful widgets (Android 12+)

Android 12 also adds stateful CheckBox, Switch, and RadioButton support in widget layouts via RemoteViews.setCompoundButtonChecked(...) and setOnCheckedChangeResponse(...) — see Create a simple widget for details.

Reference

Finding these guides helpful?

We need help from the broader community to improve these guides, add new topics and keep the topics up-to-date. See our contribution guidelines here and our topic issues list for great ways to help out.

Check these same guides through our standalone viewer for a better browsing experience and an improved search. Follow us on twitter @codepath for access to more useful Android development resources.

Clone this wiki locally