Android Core

Android Full App, Part 8: Creating an AppWidget for the home screen

This is the eight part of the “Android Full Application Tutorial” series. The complete application aims to provide an easy way of performing movies/actors searching over the internet. In the first part of the series (“Main Activity UI”), we created the Eclipse project and set up a basic interface for the main activity of the application. In the second part (“Using the HTTP API”), we used the Apache HTTP client library in order to consume an external HTTP API and integrate the API’s searching capabilities into our application. In the third part (“Parsing the XML response”) we saw how to parse the XML response using Android’s built-in XML parsing capabilities. In the fourth part (“Performing the API request asynchronously from the main activity”), we tied together the HTTP retriever and XML parser services in order to perform the API search request from our application’s main activity. The request was executed asynchronously in a background thread in order to avoid blocking the main UI thread. In the fifth part (“Launching new activities with intents”), we saw how to launch a new Activity and how to transfer data from one Activity to another. In the sixth part, (“Customized list view for data presentation”) we created a custom list view in order to provide a better data visual presentation. In the seventh (“Using options menus and customized dialogs for user interaction”), we created options menus and custom dialogs in order to facilitate better user interaction. In this part, we are going to create an AppWidget for the user home screen and provide application related updates via it.

Since version 1.5, the Android SDK includes the AppWidget framework, a framework that allows developers to write “widgets” that people can drop onto their home screen and interact with. The use of widgets is very handy since it allows the user to add their favorite applications into their home screen and interact with them quickly and without having to launch the whole application. Before continuing, I suggest taking a look at the article “Introducing home screen widgets and the AppWidget framework” posted at the official Android developers blog. An example of a widget is shown in the next image. It is the one built for the purposes of the article and it gives updates on the “Word of the day”. The source code for that can be found here.

For our application, we are going to create a widget which periodically provides updates on the latest movie created in the TMDb database. As I have mentioned in the past, we have been using the very cool TMDb API for movie/actors search and various other related functions.

The first step to creating the app widget is to provide a declaration for it as well as some XML metadata that describe it. This is done by adding a special XML file in the “res/xml” folder of our project. Through that file, we provide information regarding the widget’s dimensions, its update interval etc. The corresponding class is named AppWidgetProviderInfo and its fields correspond to the fields in the XML tag we are going to see below. For the height and width of the widget, Google recommends using a specific formula:

Minimum size in dip = (Number of cells * 74dip) – 2dip

Since we wish our widget to occupy 2 cells in width and 1 cell in height, the sizes in dip are going to be 146 and 72 respectively. The update interval is defined in milliseconds and for demonstration purposes we are using only 10 seconds (10000 millis). However, please note that short interval are discouraged. More specifically, updating more frequently than every hour can quickly eat up battery and bandwidth.

UPDATE: For this very reason of avoiding battery exhaustion, Google has changed its API after version 1.6 so that the refresh rate can not be less than 30 minutes. If you wish to achieve more frequent updates, the alarm mechanism has to be used in order to send intent broadcast to your widget receiver. You can find an example of this approach in the post “App Widget using Alarm Manager“.

Last but not least, a layout has to be used so that we can handle how the widget will be rendered. This will be done by referencing an other XML file, named “widget_layout.xml”.

The XML widget declaration file is named “movie_search_widget.xml” and it is the following:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="146dip"
    android:minHeight="72dip"
    android:updatePeriodMillis="10000"
    android:initialLayout="@layout/widget_layout"
/>

Let’s see now what its layout description (“/res/layout/widget_layout.xml”) looks like:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/widget"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:focusable="true"
    style="@style/WidgetBackground">
    
    <TextView
        android:id="@+id/app_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="14dip"
        android:layout_marginBottom="1dip"
        android:includeFontPadding="false"
        android:singleLine="true"
        android:ellipsize="end"
        style="@style/Text.WordTitle" />
    
    <TextView
        android:id="@+id/movie_name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/app_name"
        android:paddingRight="5dip"
        android:paddingBottom="4dip"
        android:includeFontPadding="false"
        android:lineSpacingMultiplier="0.9"
        android:maxLines="4"
        android:fadingEdge="vertical"
        style="@style/Text.Movie" />
    
</RelativeLayout>

The layout is pretty simple. We are using RelativeLayout, where the positions of the children can be described in relation to each other or to the parent, and a couple of TextViews. Note that the style used is actually defined in a different file (“res/values/styles.xml”) in order to gather all style related attributes in one place. The style declarations are the following:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="WidgetBackground">
        <item name="android:background">@drawable/widget_bg</item>
    </style>
    
    <style name="Text">
    </style>
    
    <style name="Text.Title">
        <item name="android:textSize">16sp</item>
        <item name="android:textStyle">bold</item>
        <item name="android:textColor">@android:color/black</item>
    </style>
    
    <style name="Text.Movie">
        <item name="android:textSize">13sp</item>
        <item name="android:textColor">@android:color/black</item>
    </style>
     
</resources>

The next step is to register a special BroadcastReceiver in the AndroidManifest.xml file. This receiver will process any app widget updates that will be triggered by the system when the time comes. Let’s see the corresponding manifest file snippet:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<application android:icon="@drawable/icon" android:label="@string/app_name">
...    
        <!-- Broadcast Receiver that will process AppWidget updates -->
        <receiver android:name=".widget.MovieSearchWidget" android:label="@string/widget_name">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data android:name="android.appwidget.provider" android:resource="@xml/movie_search_widget" />
        </receiver>
        
        <!-- Service to perform web API queries -->
        <service android:name=".widget.MovieSearchWidget$UpdateService" />       
...
</application>

The receiver class is the “com.javacodegeeks.android.apps.moviesearchapp.widget.MovieSearchWidget” which will actually use an inner service class (“UpdateService”) in order to perform the updates. Note that the actions handled by that receiver are of kind APPWIDGET_UPDATE, which is sent when it is time for the AppWidget update. In the metadata section, the widget declaration XML file location is defined.

Now let’s write the class that will receive the AppWidget requests and provide the application updates. To do so, we extend the AppWidgetProvider class, which in its turn extends the BroadcastReceiver class. Actually, the AppWidgetProvider is just a convenience class to aid in implementing an AppWidget provider and its whole functionality could be achieved by a regular BroadcastReceiver.

There are five basic methods that can be overriden in order to handle the various action requests:

  • onEnabled: Called when the first App Widget is created. Global initialization should take place here, if any.
  • onDisabled: Called when the last App Widget handled by this definition is deleted. Global cleanup should take place here, if any.
  • onUpdate: Called when the App Widget needs to update its View, which could be when the user first creates the widget. This is the most commonly used method.
  • onDeleted: Called when one or more instances of this App Widget are deleted. Cleanup for the specific instances should occur here.
  • onReceive: Handles the BroadcastReceiver actions and dispatches the requests to the methods above.

Here is our implementation:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.javacodegeeks.android.apps.moviesearchapp.widget;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.IBinder;
import android.widget.RemoteViews;
import com.javacodegeeks.android.apps.moviesearchapp.R;
import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;
import com.javacodegeeks.android.apps.moviesearchapp.services.MovieSeeker;
public class MovieSearchWidget extends AppWidgetProvider {
   
   private static final String IMDB_BASE_URL = "http://m.imdb.com/title/";
   
   @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // To prevent any ANR timeouts, we perform the update in a service
        context.startService(new Intent(context, UpdateService.class));
    }
    public static class UpdateService extends Service {
       
       private MovieSeeker movieSeeker = new MovieSeeker();
       
        @Override
        public void onStart(Intent intent, int startId) {
            // Build the widget update for today
            RemoteViews updateViews = buildUpdate(this);
            // Push update for this widget to the home screen
            ComponentName thisWidget = new ComponentName(this, MovieSearchWidget.class);
            AppWidgetManager manager = AppWidgetManager.getInstance(this);
            manager.updateAppWidget(thisWidget, updateViews);
        }
        
      public RemoteViews buildUpdate(Context context) {
           
         Movie movie = movieSeeker.findLatest();              
         
         String imdbUrl = IMDB_BASE_URL + movie.imdbId;
         // Build an update that holds the updated widget contents
         RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
         updateViews.setTextViewText(R.id.app_name, getString(R.string.app_name));
         updateViews.setTextViewText(R.id.movie_name, movie.name);
         
         Intent intent = new Intent();
         intent.setAction("android.intent.action.VIEW");
         intent.addCategory("android.intent.category.BROWSABLE");
         intent.setData(Uri.parse(imdbUrl));
         
         PendingIntent pendingIntent =
            PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
           
         updateViews.setOnClickPendingIntent(R.id.movie_name, pendingIntent);
         
         return updateViews;
      }
        @Override
        public IBinder onBind(Intent intent) {
            // We don't need to bind to this service
            return null;
        }
        
    }
}

As mentioned, we override the onUpdate method and inside that, we just start a new Service that will actually provide the updates. This is done in order to perform the time consuming actions (open network connections, downloading data etc.) in an other thread, thus avoiding any ANR timeouts. In our service, we implement the onStart method. Note that this method is now deprecated and the onStartCommand should be used. Since I am using the Android 1.5 SDK, I am going to stick with the onStart method.

For our application, I use a new method from the MovieSeeker class, which has been enhanced in order to also provide the latest movie (we will see that later). Next, we construct a RemoteViews object which will describe the view that will be displayed in another process (i.e. by the home screen). In its constructor, we provide the package name (taken by the relevant Context) and the ID of the layout that the view uses (in our case “widget_layout”). Note that we cannot directly manipulate any child views of the layout, for example by setting the view’s text or registering listeners. For that reason, we are going to use the setTextViewText method in order to provide the TextView‘s text and the setOnClickPendingIntent method in order to provide a handler for the click events.

We want to launch the browser and point to the movie’s IMDB page every time the user clicks on the widget. In order to achieve this, we first create an Intent of action ACTION_VIEW and the page’s URL as data. That intent is then encapsulated inside a PendingIntent and it is the pending intent that is used as the click handler.

When the RemoteViews object is ready, we create a ComponentName object which functions as an identifier of our BroadcastReceiver. Then, we take reference of the AppWidgetManager and use it to push updates to our home screen app widget via the updateAppWidget method.

Before launching the application, let’s see how the latest movie is retrieved. Recall that the MovieSeeker class is used for movies search. We have added a method named “findLatest” to that class which uses the Movie.getLatest API call of the TMDb API. Since the response from that call does not match the format of the existing movie search responses, we have to create an additional XML handler, called “SingleMovieHandler”. You can find the full changes in the project available for downloading at the end of this article.

Now launch the corresponding Eclipse project configuration. The emulator will probably take you straight to the application itself. Rather, hit the back button in order to return to the home screen. Check the article on how to add and remove app widgets. For the emulator, you basically have to click on an empty area and not release the mouse button. Then, the following dialog will pop-up:

Choose “Widgets” and then select the “MovieSearchAppWidget”:

When the widget gets inserted in the home screen, the relevant update method will be executed and the latest movie will be fetched. This is how the home screen will look like:

Finally, if you double click on the movie’s name, the pending intent will be fired and the Android browser will launch directed to the movies IMDB page (note that some movies will not have an associated IMDB ID and page. In that case the browser will take you to a broken IMDB page).

That’s it. AppWidget for your application.

Related Articles :

Ilias Tsagklis

Ilias is a software developer turned online entrepreneur. He is co-founder and Executive Editor at Java Code Geeks.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

9 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Devendra Chouksey
12 years ago

my question is that when i open the emulator the google and music widget is by default already on home screen…so i also want that same thing for my widget that when i open my emulator i can see my widget like google and music…
plzzz help me and reply…
i searched every where but i found nothing…

abhiroop
abhiroop
9 years ago

@Devendra Chouksey : You must have seen your app icon inside menu click the icon [LONG press] it automatically gives you option to put that on home screen and then onwards all the time you start ur emulator you can see your app icon to start with

Paul Butchart
Paul Butchart
11 years ago

Ilias, Great tutorial. Will you be updating it to reflect the changes in the IMDB API version 3? They are still supporting 2.1 and XML but 3.0 drops XML in favor of JASON.

akshita gupta
akshita gupta
10 years ago

Hey where can I download its full code? I cannot find the link on this page.

akshita gupta
akshita gupta
10 years ago

Just got the link..One more thing..where is the API-Key to be declared?

Lalit
Lalit
10 years ago

Where can i download full code….

keram
keram
9 years ago

Hello,

You said: ‘ An example of a widget is shown in the next image. It is the one built for the purposes of the article and it gives updates on the “Word of the day”.’

Can this widget be used for displaying a ‘quote of the day’ derived from a locally stored file with a list of quotes? Will that be much simpler? If yes, which parts could be then omitted or changed?

I’m completely new in this widget creation…

kuria
kuria
8 years ago

nice tutorial..
how can i get the full code

Patrick
Patrick
7 years ago

Thanks for the tutorial! Recently, we have developed an audio widget and released the code in GitHub to make it useful for everyone: https://github.com/Cleveroad/MusicBobber.

Back to top button