This codelab is part of the Advanced Android Development training course, developed by the Google Developers Training team. You will get the most value out of this course if you work through the codelabs in sequence.

For complete details about the course, see the Advanced Android Development overview.

Introduction

An app widget is a miniature app view that appears on the Android home screen and can be updated periodically with new data. App widgets display small amounts of information or perform simple functions such as showing the time, summarizing the day's calendar events, or controlling music playback.

App widgets are add-ons for an existing app and are made available to users when the app is installed on a device. Your app can have multiple widgets. You can't create a stand-alone app widget without an associated app.

Users can place widgets on any home screen panel from the widget picker. To access the widget picker, touch & hold any blank space on the home screen, and then choose Widgets. Touch & hold any widget to place it on the home screen. You can also touch & hold an already-placed widget to move or resize it, if it is resizeable.

App widgets that display data can be updated periodically to refresh that data, either by the system or by the widget's associated app, through a broadcast intent. An app widget is a broadcast receiver that accepts those intents.

App widgets can also perform actions when tapped, such as launching their associated app. You can create click handlers to perform actions for the widget as a whole, or for any view of the widget layout such as a button.

In this practical, you build a simple widget that displays data, updates itself periodically, and includes a button to refresh the data on request.

What you should already know

You should be familiar with:

What you'll learn

What you'll do

The AppWidgetSample app demonstrates a simple app widget. Because the app demonstrates app widgets, the app's main activity is minimal, with one explanatory text view:

The actual app widget has two panels for information and a button:

The panels display:

In this task, you create the starter app for the project, add a skeleton app widget to that app with Android Studio, and explore the files for that new app widget.

1.1 Build and run the app project

  1. Create a new Android project. Call it AppWidgetSample and use the Empty activity template.
  2. Open res/layout/activity_main.xml.
  3. Change the android:text attribute of the TextView to read "All the functionality for this app is in the app widget." Extract the string.
  4. Select File > New > Widget > AppWidget. Leave the widget name as NewAppWidget. Set the minimum width and minimum height to 2 cells. Leave all the other options as they are, and click Finish.

Android Studio generates all the template files you need for adding an app widget to your app. You explore those files in the next section.

  1. Build and run the project in Android Studio.
  2. When the app appears, tap the home button.
  3. Touch & hold any empty space on the home screen, then tap Widgets. A list of available widgets appears. This screen is sometimes called the "widget picker."
  4. Scroll down to AppWidgetSample section, and touch & hold the blue EXAMPLE widget. Place it in any empty spot on any home screen, and release.

The new widget appears on the home screen two cells wide and one cell high, the default widget size you defined when you created the widget. This sample widget doesn't do anything other than display the word EXAMPLE on a blue background.

  1. Add another widget. You can add multiple widgets from the same app on your home screen. This functionality is mostly useful for widgets that can be configured to display customized information. For example, different weather widgets could display the weather in different locations.
  2. Touch & hold either of the EXAMPLE widgets. Resize handles appear on the edges of the widget. You can now move the widget or change the size of the widget to take up more than the allotted cells on the home screen.
  3. To remove an EXAMPLE widget from the device, touch & hold the widget and drag it to the word Remove at the top of the screen. Removing a widget only removes that particular widget instance. Neither the second widget nor the app are removed.

1.2 Explore the AppWidgetSample files

The skeleton EXAMPLE widget that Android Studio created for you generates many files in your project. In this task you explore the files.

  1. Open res/xml/new_app_widget_info.xml.

This XML configuration file is usually called the provider-info file. The provider-info file defines several properties of your app widget, including the widget's layout file, default size, configuration activity (if any), preview image, and periodic update frequency (how often the widget updates itself, in milliseconds).

  1. Open res/layout/new_app_widget.xml.

This file defines the layout of your app widget. App widget layouts are based on RemoteViews elements, rather than the normal View hierarchy, although you define them in XML the same way. Remote views provide a separate view hierarchy that can be displayed outside an app. Remote views include a limited subset of the available Android layouts and views.

  1. Open res/drawable/example_appwidget_preview.png.
    This drawable is a PNG image that provides a preview of the widget itself. The preview image appears on the Android widget picker screen when the user installs your widget. The default preview image you get when you create the app widget is a blue rectangle with the word EXAMPLE. If there's no preview image, the icon for your app is used instead. The resource for the preview image is specified in the provider-info file.
  2. Open java/values/NewAppWidget.java.

This file is the widget provider, the Java file that defines the behavior for your widget. The key task for a widget provider is to handle widget update intents. App widgets extend the AppWidgetProvider class, which in turn extends BroadcastReceiver.

  1. Open res/values/dimens.xml.

This default dimensions file includes a value for the widget padding of 8 dp. App widgets look best with a little extra space around the edges so that the widgets do not display edge-to-edge on the user's home screen. Before Android 4.0 (API 14), your layout needed to include this padding. After API 14 the system adds the margin for you.

  1. Open res/values/dimens.xml(2)/dimen.xml (v14).

This dimensions file is used for Android API versions 14 and higher. The value of widget-margin in this file is 0 dp, because the system adds the margin for you.

  1. Open manifests/AndroidManifest.xml.

In the AndroidManifest.xml file, the widget provider is defined as a BroadcastReceiver with the <receiver> tag, because the AppWidgetProvider class extends BroadcastReceiver. The definition also includes an intent filter with an action of android.appwidget.action.APPWIDGET_UPDATE, which indicates that this app widget listens to app widget update broadcast intents. Note the android:resource attribute of the meta-data tag, which specifies the widget provider-info file (new_app_widget_info).

1.3 Explore the provider-info file

Some metadata about the app widget is defined in the widget provider-info file. In this step, you explore that file and some of the metadata it contains.

  1. Open res/xml/new_app_widget_info.xml.
  2. Note the android:initialLayout attribute.

This attribute defines the layout resource that your app widget will use, in this case the @layout/new_app_widget layout file.

  1. Note the android:minHeight and android:minWidth attributes.

These attributes define the minimum initial size of the widget, in dp. When you defined your widget in Android Studio to be 2 cells wide by 2 high, Android Studio fills in these values in the provider-info file. When your widget is added to a user's home screen, it is stretched both horizontally and vertically to occupy as many grid cells as satisfy the minWidth and minHeight values.

The rule for how many dp fit into a grid cell is based on the equation 70 × grid_size − 30, where grid_size is the number of cells you want your widget to take up. Generally speaking, you can use this table to determine what your minWidth and minHeight should be:

# of cells (columns or rows)

minWidth or minHeight

1

40 dp

2

110 dp

3

180 dp

4

250 dp

In this task, you learn how home screen cells translate to actual widget dimensions. You create the widget layout views in the layout editor, and you edit the widget provider to build and display the layout.

2.1 Add the widget layout

  1. Open res/layout/new_app_widget.xml.
  2. Note the value for android:padding in the top-level RelativeLayout element. This padding value is defined in the dimens.xml files (@dimen/widget_margin), and varies depending on the version of Android on which the widget is running.
  3. Delete the existing TextView element. Add a LinearLayout element inside the RelativeLayout element with these attributes:

Attribute

Value

android:id

"@+id/section_id"

android:layout_width

"match_parent"

android:layout_height

"wrap_content"

android:layout_alignParentLeft

"true"

android:layout_alignParentStart

"true"

android:layout_alignParentTop

"true"

android:orientation

"horizontal"

android:style

"@style/AppWidgetSection"

This linear layout provides the appearance of a light-colored panel on top of a grey-blue background. The AppWidgetSection style does not yet exist and appears in red in Android Studio. (You add it later.)

  1. Inside the LinearLayout, add a TextView with these attributes:

Attribute

Value

android:id

"@+id/appwidget_id_label"

android:layout_width

"0dp"

android:layout_height

"wrap_content"

android:layout_weight

"2"

android:text

"Widget ID"

style

"@style/AppWidgetLabel"

Extract the string for the text. This text view is the label for the widget ID. The AppWidgetLabel style does not yet exist.

  1. Add a second TextView below the first one, and give it these attributes:

Attribute

Value

android:id

"@+id/appwidget_id"

android:layout_width

"0dp"

android:layout_height

"wrap_content"

android:layout_weight

"1"

android:text

"XX"

style

"@style/AppWidgetText"

You do not need to extract the string for the text in this text view, because the string is replaced with the actual ID when the app widget runs. As with the previous views, the AppWidgetText style is not yet defined.

  1. Open res/values/styles.xml. Add the following code below the AppTheme styles to define AppWidgetSection, AppWidgetLabel, and AppWidgetText:
<style name="AppWidgetSection" parent="@android:style/Widget">
   <item name="android:padding">8dp</item>
   <item name="android:layout_marginTop">12dp</item>
   <item name="android:layout_marginLeft">12dp</item>
   <item name="android:layout_marginRight">12dp</item>
   <item name="android:background">@android:color/white</item>
</style>

<style name="AppWidgetLabel" parent="AppWidgetText">
   <item name="android:textStyle">bold</item>
</style>

<style name="AppWidgetText" parent="Base.TextAppearance.AppCompat.Subhead">
   <item name="android:textColor">@android:color/black</item>
</style>
  1. Return to the app widget's layout file. Click the Design tab to examine the layout in the design editor.
    By default, Android Studio assumes that you are designing a layout for a regular activity, and it displays a default activity "skin" around your layout. Android Studio does not provide a preview for app widget designs.

TIP: To simulate a simple widget design, choose Android Wear Square from the device menu and resize the layout to be approximately 110 dp wide and 110 dp tall (the values of minWidth and minHeight in the provider-info file).

2.2 Build the widget views in the widget provider

The widget-provider class is a subclass of AppWidgetProvider. You must implement the onUpdate() method for every app widget. This method is called the first time the widget runs and again each time the widget receives an update request (a broadcast intent).

Implementing a widget update typically involves these tasks:

Unlike activities, where you only inflate the layout once and then modify it in place as new data appears, the entire app widget layout must be reconstructed and redisplayed each time the widget receives an update intent.

  1. Open java/values/NewAppWidget.java.
  2. Scroll down to the onUpdate() method, and examine the method parameters.

The onUpdate() method is called with several arguments including the context, the app widget manager, and an array of integers that contains all the available app widget IDs.

Every app widget that the user adds to the home screen gets a unique internal ID that identifies that app widget. Each time you get an update request in your provider class, you must update all app widget instances by iterating over that array of IDs.

The template code that Android Studio defines for your widget provider's onUpdate() method iterates over that array of app widget IDs and calls the updateAppWidget() helper method.

@Override
public void onUpdate(Context context, 
      AppWidgetManager appWidgetManager, int[] appWidgetIds) {
      // There may be multiple widgets active, so update all of them
      for (int appWidgetId : appWidgetIds) {
         updateAppWidget(context, appWidgetManager, appWidgetId);
      }
}

When you use this template code for an app widget, you do not need to modify the actual onUpdate() method. Use the updateAppWidget() helper method to update each individual widget.

  1. In the updateAppWidget() method, delete the line that gets the app widget text:
CharSequence widgetText = 
   context.getString(R.string.appwidget_text);

The template code for the app widget includes this string. You won't use it for this app widget.

  1. Modify the arguments to the views.setTextViewText() method to update the R.id.appwidget_id view with the actual appWidgetId.
views.setTextViewText(R.id.appwidget_id, String.valueOf(appWidgetId));
  1. Delete the onEnabled() and onDisabled() method stubs.

You would use onEnabled() to perform initial setup for a widget (such as opening a new database) when the first instance is initially added to the user's home screen. Even if the user adds multiple widgets, this method is only called once. Use onDisabled(), correspondingly, to clean up any resources that were created in onEnabled() once the last instance of that widget is removed. You won't use either of these methods for this app, so you can delete them.

  1. Build and run the app. When the app launches, go to the device's Home screen.

Delete the existing EXAMPLE widget from the home screen, and add a new widget. The preview for your app widget in the widget picker still uses an image that represents the EXAMPLE widget. When you place the new widget on a home screen, the widget should show the new layout with the internal widget ID. Note that the current height of the widget leaves a lot of space below the panel for the ID. You'll add more panels soon.

Note: Because app widgets are updated independently from their associated app, sometimes when you make changes to an app widget in Android Studio those changes do not show up in existing apps. When testing widgets make sure to remove all existing widgets before adding new ones.

  1. Add a second copy of the app widget to the home screen. Note that each widget has its own widget ID.

The data your app widget contains can be updated in two ways:

In both these cases the app widget manager sends a broadcast intent with the action ACTION_APPWIDGET_UPDATE. Your app widget-provider class receives that intent, and calls the onUpdate() method.

3.1 Handle periodic updates

In this task, you add a second panel to the app widget that indicates how many times the widget has been updated, and the last update time.

  1. In the widget layout file, add a second LinearLayout element just after the first, and give it these attributes:

Attribute

Value

android:id

"@+id/section_update"

android:layout_width

"match_parent"

android:layout_height

"wrap_content"

android:layout_alignParentLeft

"true"

android:layout_alignParentStart

"true"

android:layout_below

"@+id/section_id"

android:orientation

"vertical"

style

"@style/AppWidgetSection"

  1. Inside the new LinearLayout, add a TextView with these attributes, and extract the string:

Attribute

Value

android:id

"@+id/appwidget_update_label"

android:layout_width

"match_parent"

android:layout_height

"wrap_content"

android:layout_marginBottom

"2dp"

android:text

"Last Updated"

style

"@style/AppWidgetLabel"

  1. Add a second TextView after the first one and give it these attributes:

Attribute

Value

android:id

"@+id/appwidget_update"

android:layout_width

"match_parent"

android:layout_height

"wrap_content"

android:layout_weight

"1"

android:text

"%1$d @%2$s"

style

"@style/AppWidgetText"

Extract the string in android:text and give it the name date_count_format. Parts of this string will be replaced in the Java code for your app, with the formatting codes filled in with numeric values. In this case the formatting code has four parts:

The parts of the string that are not placeholders (here, just the @ sign) are passed through to the new string. You can find out more about placeholders and formatting codes in the Formatter documentation.

  1. In the app widget provider-info file (res/xml/new_app_widget_info.xml), change the android:minHeight attribute to 180dp.
android:minHeight="180dp"

When you add more content to the layout, the default size of the widget in cells on the home screen also needs to change. This iteration of the app widget is 3 cells high by 2 wide, which means minHeight is now 180 dp and minWidth remains 110 dp.

  1. Also in the provider-info file, change android:updatePeriodMillis to 1800000.

The android:updatePeriodMillis attribute defines how often the app widget is updated. The default update interval is 86,400,000 milliseconds (24 hours). 1,800,000 milliseconds is a 30 minute interval. If you set updatePeriodMillis to less than 1,800,000 milliseconds, the app widget manager only sends update requests every 30 minutes. Because updates use system resources, even if the associated app is not running, you should generally avoid frequent app widget updates.

  1. In the app widget provider (NewAppWidget.java), add static variables to the top of the class for shared preferences. You'll use shared preferences to keep track of the current update count for the widget.
private static final String mSharedPrefFile = 
   "com.example.android.appwidgetsample";
private static final String COUNT_KEY = "count";
  1. At the top of the updateAppWidget() method, get the value of the update count from the shared preferences, and increment that value.
SharedPreferences prefs = context.getSharedPreferences(
   mSharedPrefFile, 0);
int count = prefs.getInt(COUNT_KEY + appWidgetId, 0);
count++;

The key to get the current count out of the shared preferences includes both the static key COUNT_KEY and the current app widget ID. Each app widget may have a different update count so each app widget needs its own entry in the shared preferences.

  1. Get the current time and format it as a short string (DateFormat.SHORT):
String dateString = 
   DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date());

You will need to import both the DateFormat (java.text.DateFormat) and Date (java.util.Date) classes.

  1. After updating the text view for appwidget_id, add a line to update the appwidget_update text view. This code gets the date format string from the resources and substitutes the formatting codes in the string with the actual values for the number of updates (count) and the current update time (dateString):
views.setTextViewText(R.id.appwidget_update,
       context.getResources().getString(
           R.string.date_count_format, count, dateString));
  1. After constructing the RemoteViews object and before requesting the update from the app widget manager, put the current update count back into shared preferences:
SharedPreferences.Editor prefEditor = prefs.edit();
prefEditor.putInt(COUNT_KEY + appWidgetId, count);
prefEditor.apply();

As with the earlier lines where you retrieved the count from the shared preferences, here you store the count with a key that includes COUNT_KEY and the app widget ID, to differentiate between different counts.

  1. Compile and run the app. As before, make sure you remove any existing widgets.
  2. Add two widgets to the home screen, at least one minute apart. The widgets will have the same update count (1) but different update times.

At each half an hour interval after you add an app widget to the home screen, the Android app widget manager sends an update broadcast intent. Your widget provider accepts that intent, increments the count and updates the time for each widget. You could wait half an hour to see the widgets update itself, but in the next section you add a button that manually triggers an update.

The automatic update interval is timed from the first instance of the widget you placed on the home screen. Once that first widget receives an update request, then all the widget instances are updated at the same time.

3.2 Add an update button

In this last task, you add a button to the widget that explicitly requests a widget update with a broadcast intent. With this button you can update your widgets on request without waiting for the widget manager to get around to updating your widgets.

  1. In the widget layout file, add a Button element just below the second LinearLayout, and give it these attributes:

Attribute

Value

android:id

"@+id/button_update"

android:layout_width

"wrap_content"

android:layout_height

"wrap_content"

android:layout_below

"@+id/section_update"

android:layout_centerHorizontal

"true"

android:text

"Update now"

style

"@style/AppWidgetButton"

Again, you may need to enlarge the widget in the Design tab.

  1. In styles.xml, add this style for the button:
<style name="AppWidgetButton" parent="Base.Widget.AppCompat.Button">
   <item name="android:layout_marginTop">12dp</item>
</style>
  1. In your AppWidgetProvider class, in the updateAppWidget() method, create an intent and set that intent's action to AppWidgetManager.ACTION_APPWIDGET_UPDATE. Add these lines just after you save the count to the shared preferences, and before the final call to appWidgetManager.updateAppWidget().
 Intent intentUpdate = new Intent(context, NewAppWidget.class);
 intentUpdate.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);

The new intent is an explicit intent with the widget-provider class (NewAppWidget.class) as the target component.

  1. After the lines to create the intent, create an array of integers with only one element: the current app widget ID.
int[] idArray = new int[]{appWidgetId};
  1. Add an intent extra with the key AppWidgetManager.EXTRA_APPWIDGET_IDS, and the array you just created.
intentUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, idArray);

The intent needs an array of app widget IDs to update. In this case there's only the current widget ID, but that ID still needs to be wrapped in an array.

  1. Use the PendingIntent.getBroadcast() method to wrap the intent as a pending intent that will perform a broadcast.
PendingIntent pendingUpdate = PendingIntent.getBroadcast(
   context, appWidgetId, intentUpdate, 
   PendingIntent.FLAG_UPDATE_CURRENT);

You use a pending intent here because the app widget manager sends the broadcast intent on your behalf.

  1. Set the onClick listener for the button to send the pending intent. Specifically for this action, the RemoteViews class provides a shortcut method called setOnClickPendingIntent().
views.setOnClickPendingIntent(R.id.button_update, pendingUpdate);

TIP: In this step, a single view (the button) sends a pending intent. To have the entire widget send a pending intent, give an ID to the top-level widget layout view. Specify that ID as the first argument in the setOnClickPendingIntent() method.

  1. Compile and run the app. Remove all existing app widgets from the home screen, and add a new widget. When you tap the Update now button, both the update count and the time update.

  1. Add a second widget. Confirm that tapping the Update now button in one widget only updates that particular widget and not the other widget.

Android Studio project: AppWidgetSample

Challenge: Update the widget preview image with a screenshot of the actual app widget.

# of columns or rows

minWidth or minHeight

1

40 dp

2

110 dp

3

180 dp

4

250 dp

The related concept documentation is in 2.1 App widgets.

Android developer documentation:

This section lists possible homework assignments for students who are working through this codelab as part of a course led by an instructor. It's up to the instructor to do the following:

Instructors can use these suggestions as little or as much as they want, and should feel free to assign any other homework they feel is appropriate.

If you're working through this codelab on your own, feel free to use these homework assignments to test your knowledge.

Build and run an app

Create an app that has an EditText view and a button. When the user taps the button, save the string that's displayed in the EditText view to the shared preferences. Ensure that the app loads this string from the preferences in onCreate().

Add a 1x3 app widget that displays the current value of the string. Add a click handler to the entire app widget such that the app opens when the user taps the app widget.

Answer these questions

Question 1

Which of these app-widget components are required? (Choose all that apply)

Question 2

Which of these layout and view classes can be used in an app widget?

Question 3

In which method in your widget-provider class do you initialize the layout (remote views) for the app widget?

Submit your app for grading

Guidance for graders

Check that the app has the following features:

To see all the codelabs in the Advanced Android Development training course, visit the Advanced Android Development codelabs landing page.