Sir Timothy John Berners-Lee, painted portrait...

Sir Timothy John Berners-Lee, painted portrait _DDC7898 (Photo credit: Abode of Chaos)

Sir Timothy John Berners-Lee, painted portrait...

Sir Timothy John Berners-Lee, painted portrait DSC_0163 (Photo credit: Abode of Chaos)

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

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

01 <?xml version="1.0" encoding="utf-8"?>
02
03 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
04     android:id="@+id/widget"
05     android:layout_width="fill_parent"
06     android:layout_height="wrap_content"
07     android:focusable="true"
08     style="@style/WidgetBackground">
09     
10     <TextView
11         android:id="@+id/app_name"
12         android:layout_width="wrap_content"
13         android:layout_height="wrap_content"
14         android:layout_marginTop="14dip"
15         android:layout_marginBottom="1dip"
16         android:includeFontPadding="false"
17         android:singleLine="true"
18         android:ellipsize="end"
19         style="@style/Text.WordTitle" />
20     
21     <TextView
22         android:id="@+id/movie_name"
23         android:layout_width="fill_parent"
24         android:layout_height="wrap_content"
25         android:layout_below="@id/app_name"
26         android:paddingRight="5dip"
27         android:paddingBottom="4dip"
28         android:includeFontPadding="false"
29         android:lineSpacingMultiplier="0.9"
30         android:maxLines="4"
31         android:fadingEdge="vertical"
32         style="@style/Text.Movie" />
33     
34 </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 <?xml version="1.0" encoding="utf-8"?>
02
03 <resources>
04
05     <style name="WidgetBackground">
06         <item name="android:background">@drawable/widget_bg</item>
07     </style>
08     
09     <style name="Text">
10     </style>
11     
12     <style name="Text.Title">
13         <item name="android:textSize">16sp</item>
14         <item name="android:textStyle">bold</item>
15         <item name="android:textColor">@android:color/black</item>
16     </style>
17     
18     <style name="Text.Movie">
19         <item name="android:textSize">13sp</item>
20         <item name="android:textColor">@android:color/black</item>
21     </style>
22      
23 </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 <application android:icon="@drawable/icon" android:label="@string/app_name">
02 ...    
03         <!-- Broadcast Receiver that will process AppWidget updates -->
04         <receiver android:name=".widget.MovieSearchWidget" android:label="@string/widget_name">
05             <intent-filter>
06                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
07             </intent-filter>
08             <meta-data android:name="android.appwidget.provider" android:resource="@xml/movie_search_widget" />
09         </receiver>
10         
11         <!-- Service to perform web API queries -->
12         <service android:name=".widget.MovieSearchWidget$UpdateService" />       
13
14 ...
15 </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 package com.javacodegeeks.android.apps.moviesearchapp.widget;
02
03 import android.app.PendingIntent;
04 import android.app.Service;
05 import android.appwidget.AppWidgetManager;
06 import android.appwidget.AppWidgetProvider;
07 import android.content.ComponentName;
08 import android.content.Context;
09 import android.content.Intent;
10 import android.net.Uri;
11 import android.os.IBinder;
12 import android.widget.RemoteViews;
13
14 import com.javacodegeeks.android.apps.moviesearchapp.R;
15 import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;
16 import com.javacodegeeks.android.apps.moviesearchapp.services.MovieSeeker;
17
18 public class MovieSearchWidget extends AppWidgetProvider {
19    
20    private static final String IMDB_BASE_URL = "http://m.imdb.com/title/";
21    
22    @Override
23     public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
24         // To prevent any ANR timeouts, we perform the update in a service
25         context.startService(new Intent(context, UpdateService.class));
26     }
27
28     public static class UpdateService extends Service {
29        
30        private MovieSeeker movieSeeker = new MovieSeeker();
31        
32         @Override
33         public void onStart(Intent intent, int startId) {
34             // Build the widget update for today
35             RemoteViews updateViews = buildUpdate(this);
36
37             // Push update for this widget to the home screen
38             ComponentName thisWidget = new ComponentName(this, MovieSearchWidget.class);
39             AppWidgetManager manager = AppWidgetManager.getInstance(this);
40             manager.updateAppWidget(thisWidget, updateViews);
41         }
42         
43       public RemoteViews buildUpdate(Context context) {
44            
45          Movie movie = movieSeeker.findLatest();              
46          
47          String imdbUrl = IMDB_BASE_URL + movie.imdbId;
48
49          // Build an update that holds the updated widget contents
50          RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
51
52          updateViews.setTextViewText(R.id.app_name, getString(R.string.app_name));
53          updateViews.setTextViewText(R.id.movie_name, movie.name);
54          
55          Intent intent = new Intent();
56          intent.setAction("android.intent.action.VIEW");
57          intent.addCategory("android.intent.category.BROWSABLE");
58          intent.setData(Uri.parse(imdbUrl));
59          
60          PendingIntent pendingIntent =
61             PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
62            
63          updateViews.setOnClickPendingIntent(R.id.movie_name, pendingIntent);
64          
65          return updateViews;
66
67       }
68
69         @Override
70         public IBinder onBind(Intent intent) {
71             // We don't need to bind to this service
72             return null;
73         }
74         
75     }
76
77 }

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. You can download here the Eclipse project created so far

Advertisements
Comments
  1. free software download for windows 7 says:

    Hi there, I enjoy reading all of your article post. I wanted to write a little comment to support you.

    Like

  2. Deetta Guetierrez says:

    Appreciate it for all your efforts that you have put in this. Very interesting information. “Do not consider painful what is good for you.” by Euripides.

    Like

  3. Ahmad Bilansky says:

    I like this web site because so much utile material on here : D.

    Like

  4. e-tupakka suomi says:

    I like this website very much, Its a very nice spot to read and incur info. “It is not what we take up, but what we give up, that makes us rich.” by Henry Ward Beecher.

    Like

  5. certified nursing assistant exam says:

    I’m really inspired along with your writing talents as smartly as with the layout on your blog. Is this a paid subject matter or did you modify it your self? Either way stay up the nice quality writing, it’s uncommon to peer a nice weblog like this one nowadays.

    Like

  6. Broadjam scam says:

    Its like you read my mind! You seem to understand so much about this, such as you wrote the e-book in it or something. I think that you simply can do with a few percent to drive the message home a little bit, however other than that, this is wonderful blog. An excellent read. I’ll definitely be back.

    Like

  7. seo secrets says:

    Appreciating the determination you put into your website and in depth information you present.

    Like

  8. breast cancer juliana says:

    I got what you mean , thankyou for putting up.Woh I am thankful to find this website through google. “If one does not know to which port one is sailing, no wind is favorable.” by Seneca.

    Like

  9. Tuyet Mariscal says:

    I truly enjoy studying on this web site , it holds great posts .

    Like

  10. basketball flat panel tv says:

    Simply wanna tell that this is handy , Thanks for taking your time to write this. “We do not attract what we want, But what we are.” by James Allen.

    Like

  11. hire a crane says:

    Appreciate it for helping out, excellent info. “Whoever obeys the gods, to him they particularly listen.” by Homer.

    Like

  12. kore dizileri izle says:

    i cant get how you may share like this incredible posts admin a lot thanks

    Like

  13. website hosting says:

    I was studying some of your content on this site and I conceive this website is very informative ! Keep posting .

    Like

  14. credit card consolidation says:

    Somebody necessarily help to make critically posts I’d state. That is the first time I frequented your web page and thus far? I surprised with the research you made to make this particular publish amazing. Wonderful task!

    Like

  15. Gloria says:

    Keep up the fantastic piece of work, I read few content on this internet site and I believe that your weblog is really interesting and has lots of great info.

    Like

  16. four micro onde says:

    I was reading through some of your posts on this website and I conceive this site is really instructive! Keep on putting up.

    Like

  17. navy seal physical fitness standards says:

    Woh I love your blog posts, saved to bookmarks ! .

    Like

  18. computer mouses says:

    Great Share! This really answered my problem, thanks!

    Like

  19. I was just looking for this info for a while. After six hours of continuous Googleing, at last I got it in your web site. I wonder what is the lack of Google strategy that do not rank this type of informative sites in top of the list. Generally the top websites are full of garbage.

    Like

  20. Hi, i believe that i noticed you visited my blog thus i came to “go back the want”.I’m trying to find things to enhance my site!I assume its good enough to use a few of your concepts!!

    Like

  21. Eva Angelina says:

    Very interesting points you have observed, regards for posting.

    Like

  22. disque dur externe pas cher says:

    I genuinely enjoy looking at on this website, it contains wonderful posts. “One should die proudly when it is no longer possible to live proudly.” by Friedrich Wilhelm Nietzsche.

    Like

  23. Dortha Uram says:

    You have brought up a very fantastic points , regards for the post.

    Like

  24. hyip monitor admin says:

    Wow! Thank you! I constantly wanted to write on my website something like that. Can I include a part of your post to my website?

    Like

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s