Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Creating a Simple App Widget

Author: Catarina Reis -- Published? true -- FormatLanguage: W

Problem:

Widgets are simple graphical user interfaces that allow users to easily interact with an existing application (activity and/or service). It's rather simple to create one. Just try it!

Solution:

A guided list of steps with a simple example that allows you to create a widget that starts a service that updates its visual components.

Example: CurrentMoodWidget. A simple solution that presents the current mood in the form of a smiley text in a widget. The current mood smiley changes to a random mood smiley whenever the user clicks the update button (smiley image button).

Initial
Initial Mood

Mood Updated
Current Mood

Discussion:

Following these steps you will be able to create a widget that calls a service and updates its visual components.

1. Create a new Android Project (CurrentMoodWidgetProject)

  • "Current Mood" as the application name;
  • oreillymedia.cookbook.android.spikes for the package name;
  • Do not create an activity;
  • Minimum SDK version: 8 (for Android 2.2).

2. Add the text support required for the widget. Place this under the resources files folder (res/values/string.xml), according to the following name-value pairs.

  • widgettext - "current mood:"
  • widgetmoodtext - ":)"

3. Add the image(s) that will appear in the widget's button. Place these under the res/drawable structure (smile_icon.png).

4. Create a new layout file inside res/layout. Place these under the project structure, that will define the widget layout (widgetlayout.xml) according to the following structure.

	<TextView android:text="@string/widgettext" 
                  android:layout_width="0dp"
                  android:layout_height="wrap_content"
                  android:layout_weight="0.8"
                  android:layout_gravity="center_vertical"
                  android:textColor="#000000"></TextView>
	<TextView android:text="@string/widgetmoodtext"
                  android:id="@+id/widgetMood" android:layout_width="0dp" 
                  android:layout_height="wrap_content" 
                  android:layout_weight="0.3" 
                  android:layout_gravity="center_vertical" 
                  android:textColor="#000000"></TextView>
	<ImageButton android:id="@+id/widgetBtn" android:layout_width="0dp" 
                  android:layout_height="wrap_content" 
                  android:layout_weight="0.5" android:src="@drawable/smile_icon" 
                  android:layout_gravity="center_vertical"></ImageButton>

5. Now, you should provide the widget provider setup configuration:

  • Create the res/xml folder under the project structure
  • Create a xml file (widgetproviderinfo.xml) with the following parameters:
       <appwidget-provider
           xmlns:android="http://schemas.android.com/apk/res/android" 
           android:minWidth="220dp" 
           android:minHeight="72dp"
           android:updatePeriodMillis="86400000" 
           android:initialLayout="@layout/widgetlayout">
       </appwidget-provider>

6. Now you should create the service that reacts to the user interaction with the smiley image button (CurrentMoodService.java).

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		super.onStart(intent, startId);
                updateMood(intent);
		stopSelf(startId);
		return START_STICKY;
	}

	private void updateMood(Intent intent) {
           if (intent != null){
    		String requestedAction = intent.getAction();
    		if (requestedAction != null &&  requestedAction.equals(UPDATEMOOD)){
	            this.currentMood = getRandomMood();
	            int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0);
	            AppWidgetManager appWidgetMan = AppWidgetManager.getInstance(this);
	            RemoteViews views = new RemoteViews(this.getPackageName(),R.layout.widgetlayout);
	            views.setTextViewText(R.id.widgetMood, currentMood);
	            appWidgetMan.updateAppWidget(widgetId, views);  
    		}
           }
       }

7. After defining the service, it is time to implement the widget provider class (CurrentMoodWidgetProvider.java).

        @Override
	public void onUpdate(Context context, AppWidgetManager appWidgetManager,
			int[] appWidgetIds) {
		super.onUpdate(context, appWidgetManager, appWidgetIds);
		
		for (int i=0; i<appWidgetIds.length; i++) {
		    int appWidgetId = appWidgetIds[i];
		    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widgetlayout);
		    Intent intent = new Intent(context, CurrentMoodService.class);
		    intent.setAction(CurrentMoodService.UPDATEMOOD);
		    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
		    PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
		    views.setOnClickPendingIntent(R.id.widgetBtn, pendingIntent);
		    appWidgetManager.updateAppWidget(appWidgetId, views);
		}
	}	

8. Finally it is necessary to declare the Service and the App Widget Provider in the Manifest (AndroidManifest.xml).

	
    	<service android:name=".CurrentMoodService">
    	</service>
	<receiver android:name=".CurrentMoodWidgetProvider">
	    <intent-filter>
	        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
	    </intent-filter>
	    <meta-data android:name="android.appwidget.provider"
	               android:resource="@xml/widgetproviderinfo" />
	</receiver>

Download:

The source code for this project can be downloaded from http://sites.google.com/site/androidsourcecode/src/CurrentMoodWidgetProject.rar.
warrel 2014-05-16 07:10:43.173 Actually quite late, but I suppose more people are getting into issues with widgets and PendingIntents. On other sites questions are asked and given correct answers, although you actually need to know the problem, otherwise a answer is hard to find: Issue 1: First of all Android will try to re-use a PendingIntent when class and activity is the same. You can do 2 different things. Use the flag (FLAG_UPDATE_CURRENT), but be this only works if the user has exactly one widget on the screen. If you have multiple instances of the same intent and action it will re-use the intent and thus clicking the buttons on different widgets will look like clicking on the only one. The best thing is to make it unique using the second parameter for PendingIntent.getService. Be awere unique for all widgets and buttons on the widgets [(widgetId * 10) + button_id]. If the extras even are likely to change over time you should even use the FLAG_UPDATE_CURRENT to prevent old values to be send for the given extra. Issue 2: The RemoteViews instance you send to updateAppWidget should always be complete. It should always be possible to use the latest send without any knowledge of earlier send remoteviews instances. Only on later versions of Android it is possible to use partiallyUpdateAppWidget, although I could not understand the remarks about caching which are handled in the javadoc. In short the usage in this example widget is: On a onUpdate the RemoteViews is inflated and the button is attached with the PendingIntent, although after updates only the new text is send. Although it is working if you have all previous RemoteViews updates this is incorrect usage of RemoteViews and should not be promoted.
new2android 2012-10-16 18:18:48.445 If you delete and download a new version of your appWidget you may find that the widget does not update "mood" anymore when you click on the image. This is so because your appWidgetId has changed but the old PendingIntent is hanging on to the old appWidgetId. The following line in CurrentMoodWidgetProvider.java PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0); should be changed to PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); This allows contents of extras section of the old PendingIntent to be updated with the contents from the new instance of appWidget when it calls setOnClickPendingIntent. This portion of intent has the appWidgetId. So, with this change you have the new appWidgetId when the user clicks on the image and CurrentMoodService will retrieve the correct appWidget.