Android Homescreen Widgets



1. Android Widgets

1.1. Homescreen Widgets

Widgets are little applications which can be placed on the home screen of your Android device. An Widget gets its data on a periodic timetable. There are two methods to update a widget, one is based on an XML configuration file and the other is based on the Android AlarmManager service.

A Widget runs as part of the homescreen process. This requires that Widgets preserve the permissions of their application.

Widgets use RemoteViews to create there user interface. A RemoteView can be executed by another process with the same permissions as the original application. This way the Widget runs with the permissions of its defining application.

The user interface for an Widget is defined by an BroadcastReceiver. This BroadcastReceiver inflates its layout into an object of type RemoteViews. This RemoteViews object is delivered to Android, which hands it over the HomeScreen application.

1.2. Steps to create a Widget

To create a Widget you:

  • Define a layout file for your Widget.
  • Maintain an XML file (AppWidgetProviderInfo) which describes the properties of the widget, e.g. size or the fixed update frequency.
  • Create a BroadcastReceiver which will be used to build the user interface of the Widgets. This receiver extends the AppWidgetProvider class which provides additional lifecycle hooks for Widgets.
  • Maintain the App Widget configuration in the AndroidManifest.xml file.
  • You can also specify a configuration Activity which is called once a new instance instance of the widget is added to the Android homescreen.

 

1.3. Register Widget

To register a widget you create a BroadcastReceiver which an intent filter for the action android.appwidget.action.APPWIDGET_UPDATE.

 

				 <receiver
       android:icon="@drawable/icon"
       android:label="Example Widget"
       android:name="MyWidgetProvider" >
       <intent-filter >
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
       </intent-filter>

       <meta-data
          android:name="android.appwidget.provider"
          android:resource="@xml/widget_info" />
</receiver>

 

The BroadcastReceiver can get an label and and icon assigned. These are used in the list of available Widgets.

You also specify the meta-data for the widget via the android:name="android.appwidget.provider" attribute. The configuration file referred to by this meta-data contains the configuration settings for the widget. If contains for example the update interface, the size and the initial layout before the first update() call on the widget.

 

				
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_layout"
    android:minHeight="72dp"
    android:minWidth="146dp"
    android:updatePeriodMillis="180000" >

</appwidget-provider>

 

1.4. Available views and layouts

A widget is restricted in the elements it can use. For layouts you can use FrameLayout, LinearLayout and RelativeLayout. As views you can use AnalogClock, Button, Chromometer, ImageButton, ImageView, ProgressBar and TextView.

As of Android 3.0 more views are available: GridView, ListView, StackView, ViewFlipper and AdapterViewFlipper.

The only interaction with is possible on the Views of a Widget is via on OnClickListener. This OnClickListener can be registered on a widget and is triggered by the user.

1.5. Widget size

A Widget will take a certain amount of cells on the homescreen. A cell is usually used to display the icon of one application. As a calculation rule you should define the size of the widget with the formula: ((Number of columns / rows)* 74) – 2. These are device independent pixels and the -2 is used to avoid rounding issues.

As of Android 3.1 a Widgets can be flexible in size, e.g. the user can make is larger or smaller. To enable this for Widgets you can use the android:resizeMode="horizontal|vertical" atttribute in the XML configuration file for the widget.

1.6. Widget updates

In the widget configuration file you can specify a fixed update interval. The system will wake up after this time interval and call your broadcast receiver to udpate the widget. The smallest update interval is 180000 milliseconds (30 minutes).

The AlarmManager allows you to be more resource efficient and to have a higher frequency of updates.

Here you see an example configuration file for a widget.

 

				
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_layout"
    android:minHeight="72dp"
    android:minWidth="146dp"
    android:updatePeriodMillis="180000" >

</appwidget-provider>

 

1.7. AppWidgetProvider

Your BroadcastReceiver extends AppWidgetProvider. The AppWidgetProvider class implements the onReceive() method, extracts the required information and calls the following widget lifecycle methods.

As you can added several instances of a widget to the homescreen you have lifecycle methods which are called only for the first instance added / removed to the homescreen and others which are called for every instance of your widget.

Table 1. Lifecycle method

Method Description
onEnabled() Called the first time an instance of your widget is added to the homescreen
onDisabled() Called once the last instance of your widget is removed from the homescreen.
onUpdate() Called for every update of the widget. Contains the ids of appWidgetIds for which an update is needed. Note that this may be all of the AppWidget instances for this provider, or just a subset of them, as stated in the methods JavaDoc. For example if more than one widget is added to the homescreen, only the last one changes (until reinstall).
onDeleted() Widget instance is removed from the homescreen

All long running operations in these methods should be performed in a service, as the execution time for a broadcast receiver is limited. Using asynchronous processing in the onReceive() method does not help as the system can kill the broadcast process after his onReceive() method.

2. Prerequisites

The following description assume that you have already experience in building standard Android application. Please see Android Tutorial . It also uses partly Android services. You find an introduction into Android Services in Android Service Tutorial .

3. Example with fixed update interval

The following will create a simple example for a widget. We will create a widget which displays a random number. This random number will updated automatically every 30 minutes. We will also register a OnClickListener so that the widgets update if the user click on it.

The resulting widget will look like the following.

 

Screenshot of the final Widget of this tutorial

 

Create a new Android project de.vogella.android.widget.example with no Activity in the package de.vogella.android.widget.example.

Create a new file myshape.xml under the path “/res/drawable-mdpi”. This file will define the background we use in your widget.

 

			
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <stroke
        android:width="2dp"
        android:color="#FFFFFFFF" />

    <gradient
        android:angle="225"
        android:endColor="#DD2ECCFA"
        android:startColor="#DD000000" />

    <corners
        android:bottomLeftRadius="7dp"
        android:bottomRightRadius="7dp"
        android:topLeftRadius="7dp"
        android:topRightRadius="7dp" />

</shape>

 

Define the following widget_layout.xml file under the res/layout folder.

 

			
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="8dip"
    android:background="@drawable/myshape" >

    <TextView
        android:id="@+id/update"
        style="@android:style/TextAppearance.Medium"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:gravity="center_horizontal|center_vertical"
        android:layout_margin="4dip"
        android:text="Static Text" >
    </TextView>

</LinearLayout>

 

Create the AppWidgetProvider metadata file “widget_info.xml”, via File → New → Android → Android XML File

 

Creating the widget provider 1

 

 

Creating the widget provider 2

 

 

			
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider 
    	xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_layout"
    android:minHeight="72dp"
    android:minWidth="300dp"
    android:updatePeriodMillis="300000" >

</appwidget-provider>

 

Create the BroadcastReeceiver which will be called for updates. We also register an OnClickListener on the view of the PendingIntent.

 

			
package de.vogella.android.widget.example;

import java.util.Random;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.RemoteViews;

public class MyWidgetProvider extends AppWidgetProvider {

	private static final String ACTION_CLICK = "ACTION_CLICK";

	@Override
	public void onUpdate(Context context, AppWidgetManager appWidgetManager,
			int[] appWidgetIds) {

		// Get all ids
		ComponentName thisWidget = new ComponentName(context,
				MyWidgetProvider.class);
		int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
		for (int widgetId : allWidgetIds) {
			// Create some random data
			int number = (new Random().nextInt(100));

			RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
					R.layout.widget_layout);
			Log.w("WidgetExample", String.valueOf(number));
			// Set the text
			remoteViews.setTextViewText(R.id.update, String.valueOf(number));

			// Register an onClickListener
			Intent intent = new Intent(context, MyWidgetProvider.class);

			intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
			intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);

			PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
					0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
			remoteViews.setOnClickPendingIntent(R.id.update, pendingIntent);
			appWidgetManager.updateAppWidget(widgetId, remoteViews);
		}
	}
}

 

Open AndroidManifest.xml and maintain the following.

 

			
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="de.vogella.android.widget.example"
    android:versionCode="1"
    android:versionName="1.0" >

    <application
        android:icon="@drawable/icon"
        android:label="@string/app_name" >
        <receiver android:name="MyWidgetProvider" >
            <intent-filter >
                <action 
                    android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
        </receiver>
    </application>

    <uses-sdk android:minSdkVersion="8" />

</manifest>

 

This attribute specifies that the AppWidgetProvider accepts the ACTION_APPWIDGET_UPDATE broadcast and specifies the metadata for the widget.

Run your app. Once your app has been deployed, long press on your desktop and install your new widget.

 

Installing the widget part 1

 

 

Installing the widget part 2

 

4. Update via a service and onClickListener

A BroadcastReceiver must finish his onReceive() method within 5 secs.

For potentially long running actions, as for example network access, you should do this processing in a service and perform the update the widgets from the service.

The following will demonstrate the usage of a service to update the widget.

Create the following UpdateWidgetService class in your project.

 

			
package de.vogella.android.widget.example;

import java.util.Random;

import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.widget.RemoteViews;

public class UpdateWidgetService extends Service {
	private static final String LOG = "de.vogella.android.widget.example";

	@Override
	public void onStart(Intent intent, int startId) {
		Log.i(LOG, "Called");
		// Create some random data

		AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this
				.getApplicationContext());

		int[] allWidgetIds = intent
				.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);

		ComponentName thisWidget = new ComponentName(getApplicationContext(),
				MyWidgetProvider.class);
		int[] allWidgetIds2 = appWidgetManager.getAppWidgetIds(thisWidget);
		Log.w(LOG, "From Intent" + String.valueOf(allWidgetIds.length));
		Log.w(LOG, "Direct" + String.valueOf(allWidgetIds2.length));

		for (int widgetId : allWidgetIds) {
			// Create some random data
			int number = (new Random().nextInt(100));

			RemoteViews remoteViews = new RemoteViews(this
					.getApplicationContext().getPackageName(),
					R.layout.widget_layout);
			Log.w("WidgetExample", String.valueOf(number));
			// Set the text
			remoteViews.setTextViewText(R.id.update,
					"Random: " + String.valueOf(number));

			// Register an onClickListener
			Intent clickIntent = new Intent(this.getApplicationContext(),
					MyWidgetProvider.class);

			clickIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
			clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS,
					allWidgetIds);

			PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, clickIntent,
					PendingIntent.FLAG_UPDATE_CURRENT);
			remoteViews.setOnClickPendingIntent(R.id.update, pendingIntent);
			appWidgetManager.updateAppWidget(widgetId, remoteViews);
		}
		stopSelf();

		super.onStart(intent, startId);
	}

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}
}

 

Add this class as a Service to your AndroidManifest.xml.

 

			
<service android:name=".UpdateWidgetService"></service>

 

Change MyWidgetProvider to the following. It will now only construct the service and start it.

 

			
package de.vogella.android.widget.example;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class MyWidgetProvider extends AppWidgetProvider {

	private static final String LOG = "de.vogella.android.widget.example";

	@Override
	public void onUpdate(Context context, AppWidgetManager appWidgetManager,
			int[] appWidgetIds) {

		Log.w(LOG, "onUpdate method called");
		// Get all ids
		ComponentName thisWidget = new ComponentName(context,
				MyWidgetProvider.class);
		int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);

		// Build the intent to call the service
		Intent intent = new Intent(context.getApplicationContext(),
				UpdateWidgetService.class);
		intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds);

		// Update the widgets via the service
		context.startService(intent);
	}
}

 

Once called this service will update all widgets. You can click on one of the widgets to update all widgets.

5. Thank you

Enhanced by Zemanta




Leave a Reply