You are on page 1of 9

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 APIs searching
capabilities into our application. In the third part (Parsing the XML response) we saw how to parse
the XML response using Androids 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 applications 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 widgets 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:

<?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"
/>

Lets see now what its layout description (/res/layout/widget_layout.xml) looks like:

<?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:

<?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. Lets see the corresponding manifest file snippet:

<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 kindAPPWIDGET_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 lets write the class that will receive the AppWidget requests and provide the application
updates. To do so, we extend theAppWidgetProvider 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:

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
theonStartCommand 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 views text or registering
listeners. For that reason, we are going to use the setTextViewText method in order to provide
the TextViews 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 movies 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 pages
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 updateAppWidgetmethod.

Before launching the application, lets 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 movies 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).

Thats it. AppWidget for your application. You can download here the Eclipse project created so far.

You might also like