Performing Network Updates

In this, the final in this series of posts about Android Widgets, we will build on the previous articles by taking our periodically updating widget and adding a background service to fetch data from the network. To make things easy for ourselves, we’ll show the most recent Tweet from a public timeline from Twitter as our datasource, but this will be easily adaptable to other sources. We’ll also add a configuration activity, prompting the user of the Widget to enter the Twitter user they want to display Tweets for, before the Widget is displayed.

If you need to catch up, the previous articles were:

Prepping the Demo

In our previous example using AlarmManager, we had a working Widget that showed a clock, which updated itself every second through the use of AlarmManager. This is the perfect starting point for us, as we will want to perform a periodic update of the data. We’ll need to dial the frequency back a bit though, to once every 5 minutes or so.

If you don’t have the source code already, grab it from my Android Examples GitHub Repository, using the frequentUpdates tag. This can be done by running the following commands. We are assuming you have command line Git set up, but of course you do.

git clone https://github.com/brucejcooper/Android-Examples.git
cd Android-Examples/WidgetExample
git tag frequentUpdates

Now we’re ready to add our networking changes

Background Tasks – Services

Performing a call to a network service, especially one as user friendly as Twitter, is relatively straight forward on Android. However we can’t just execute the call in our Widget Provider, as the network call will take a relatively long time, and we are not allowed to block the application main thread. It needs to be run in a separate thread. Again this is no biggie, but there is one further complication. Android processes have a particular lifecycle, and can be destroyed at any time by the operating system to reclaim memory. We need a way to indicate to the operating system that we are doing something in the background and shouldn’t be killed, even though we are not showing something on the screen directly. This is done via the Service class.

A Service is similar to an Activity, which we discussed in the Activities, Task & Intents, except that a Service is intended for running code in the background. It has a lifecycle, similar to that of an Activity, and can either be explicitly started, or start in reaction to an Intent. A Service can be private to the application in which it lives, or provide services to the entire system through inter process communication (IPC). In this case, we are going to create a very simple Service that is explicitly started, stops itself, and does not have any bindings.

As with all components that are exposed to the OS, your service needs to be declared in the ApplicationManifest.xml file. As our Service is very simple, we simply specify the name of the class that hosts the Service.

...
<application android:icon="@drawable/icon" android:label="@string/app_name">
    <service android:name=".TwitterFetcherService"/>
...

The implementing Service should implement the lifecycle methods onCreate(), onStartCommand(), onDestroy() and onBind(). We aren’t using binding, so onBind() can simply return null. Instead, we use onStartCommand() to spawn the thread that will actually perform the network fetch.

@Override
public void onCreate() {
    mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

    // Display a notification about us starting.  We put an icon in the status bar.
    showNotification();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.i(LOG_TAG, "Received start id " + startId + ": " + intent);

    SharedPreferences settings = getSharedPreferences(PREFS_FILE, MODE_PRIVATE);
    String username = settings.getString("twitterUser", null);

    if (fetcherThread != null &amp;&amp; fetcherThread.isAlive()) {
        Log.d(LOG_TAG, "Killing existing fetch thread, so we can start a new one");
        fetcherThread.interrupt();
    }
    fetcherThread = new Fetcher(username);
    fetcherThread.start();
    // We want this service to continue running until it is explicitly
    // stopped, so return sticky.
    // WE have no reason to exist!
    return START_STICKY;
}

@Override
public void onDestroy() {
    // Cancel the persistent notification.
    mNM.cancel(NOTIFICATION);

    if (fetcherThread.isAlive()) {
        fetcherThread.interrupt();
    }

    Log.i(LOG_TAG, "Stopped Service");
}

The code also creates a notification message, which will be shown in the notification bar of your phone. I’ve done this so that it is obvious to us as we learn that the service is running. I wouldn’t do this in a real Widget though, as it becomes a distraction to the user. They don’t care when the call is being made, only that the Widget does its job.

Making the Call

Android has the Apache HttpClient built into it, which makes calling out to the network very easy. To make things easy for this post, I have picked the relatively simple User Timeline Twitter API for fetching a user’s timeline using a simple GET operation. The URL we are fetching is:

https://api.twitter.com/1/statuses/user_timeline.json?include_entities=false&amp;include_rts=true&amp;screen_name=username&amp;count=1

This will return the supplied user’s last Tweet as JSON, along with a bunch of additional information we don’t care about. The full code for the thread that performs the fetch is shown below:

public class Fetcher extends Thread {
    private String username;

    public Fetcher(String username) {
        this.username= username;
    }

    public void run() {
        // Set a timeout on connections
        HttpParams httpParams = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(httpParams, 10 * 1000);
        HttpConnectionParams.setSoTimeout(httpParams, 10 * 1000);

        HttpClient client = new DefaultHttpClient(httpParams);

        try {
            if (username == null) {
                Log.d(LOG_TAG, "Stopping before I've started. How sad");

            } else {
                Log.d(LOG_TAG, "Fetching tweet");
                HttpGet fetch = new HttpGet("https://api.twitter.com/1/statuses/user_timeline.json?include_entities=false&amp;include_rts=true&amp;screen_name="+username+"&amp;count=1");
                if (!isInterrupted()) {
                    HttpResponse response = client.execute(fetch);
                    if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                        JSONArray tweets = new JSONArray(EntityUtils.toString(response.getEntity()));

                        String tweetTxt = Html.fromHtml(tweets.getJSONObject(0).getString("text")).toString();

                        updateWidgets(tweetTxt);
                    } else {
                        showError(null);
                        // An Error happened.  Deal with it somehow
                    }
                }
            }
        } catch (InterruptedIOException e) {
            Log.d(LOG_TAG, "I was interrupted!");
        } catch (Exception e) {
            showError(e);
        } finally {
            Log.d(LOG_TAG, "Shutting myself down");
            // We're done.  Shut ourself down.
            stopSelf();
        }
    }

    private void showError(Exception ex) {
        Log.e(LOG_TAG, "Error fetching tweets", ex);
        updateWidgets( "Fetching Tweets failed");
    }
}

Take special note of the call to stopSelf() in the finally block. Once we are finished processing, there is no need for the Service to keep running. We are using the Service in a fire and forget manner. As a result, it is the Service’s responsibility to shut it self down when its done. Thats what this call does.

Finally, we need a method to update the Widget itself, once we have the data. This is exactly the same code that we had before, except that we’ve moved it from the WidgetProvider into the Service.

public void updateWidgets(String tweetTxt) {
    // Get the widget manager and ids for this widget provider, then call the shared
    // clock update method.
    ComponentName thisAppWidget = new ComponentName(getPackageName(), ExampleAppWidgetProvider.class.getName());
    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
    int ids[] = appWidgetManager.getAppWidgetIds(thisAppWidget);
    for (int appWidgetID: ids) {

        if (pendingIntent == null) {
            // Create an Intent to launch ExampleActivity
            Intent intent = new Intent(this, WidgetExampleActivity.class);
            pendingIntent = PendingIntent.getActivity(this, 0,  intent, 0);
        }

        RemoteViews updateViews = new RemoteViews(getPackageName(), R.layout.widget1);

        // Set the Button Action
        updateViews.setOnClickPendingIntent(R.id.button, pendingIntent);

        // Update the text.
        updateViews.setTextViewText(R.id.widget1label, tweetTxt);
        appWidgetManager.updateAppWidget(appWidgetID, updateViews);


    }
}

Now that we’ve got our Service code set up, its simply a matter of changing our App Provider class ExampleAppWidgetProvider to call out to our Service, rather than updating the Widget text to be the time as it currently is. To launch the Service directly, a call to startService() is made. Open up ExampleAppWidgetProvider, remove the code that currently updates the text, and replace it with a call to this method:

public static void triggerUpdate(Context context) {
    // Start the service which will load the twitter data from the server.
    context.startService(new Intent(context, TwitterFetcherService.class));

}

Configuring the Widget

We’ve now got the Widget loading data from the Internet, but how do we know which user we want to fetch data for? Many Widgets require configuration, and Android provides an easy way to add a configuration Activity that is shown before the Widget is created. The configuration Activity is specified in the Widget meta-data file. In this case, the meta-data file is located in res/xml/widget1_info.xml. Alter it to include a android:configure attribute. After we make the change, the file should look like this:

<?xml version="1.0" encoding="utf-8"?>
    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" 
    android:minWidth="294dp"
    android:minHeight="72dp"
    android:updatePeriodMillis="1800000"
    android:configure="com.eightbitcloud.example.widget.ConfigureActivity" 
    android:initialLayout="@layout/widget1"
    >
</appwidget-provider>

The Activity is a standard Activity, and can be written like any other, with two exceptions. Firstly, the Activity specification in the AndroidManifest.xml file should contain a filter for the android.appwidget.action.APPWIDGET_CONFIGURE activity.

<activity android:name="com.eightbitcloud.example.widget.ConfigureActivity" android:label="@string/configure_title">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
    </intent-filter>
</activity>

Make sure that the class name is fully qualified here, as the class will be looked up by the Widget container (the home app). Secondly, the Activity should return a result when configuration is completed, so that the Widget container knows that the Widget configuration is complete.

public class ConfigureActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.configure);
    }


    public void createClicked(View view) {
        String username = ((TextView)findViewById(R.id.configure_username)).getText().toString().trim();

        if (username == null || username.length() == 0) {
            Toast.makeText(this,  "Username is required", Toast.LENGTH_SHORT);
            return;
        }

        Intent intent = getIntent();
        Bundle extras = intent.getExtras();
        if (extras != null) {
            int mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);

            Log.d("ConfigureActivity", "App Widget ID is " + mAppWidgetId);

            // Save the username to application preferences
            Editor settings = getSharedPreferences(TwitterFetcherService.PREFS_FILE, MODE_PRIVATE).edit();
            settings.putString("twitterUser", username);
            settings.apply();

            // Kick off a refresh, as when you have a configure activity android does not do one automatically the first time.
            startService(new Intent(this, TwitterFetcherService.class));


            // Tell android that we're done
            Intent resultValue = new Intent();
            resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
            setResult(RESULT_OK, resultValue);
            finish();
        }
    }
}

Wrap Up

And there we have it. After three articles, we have a fully network enabled, configurable, frequently updating Android Widget. Projects set up like this will be the basis of just about every widget out there. I hope you’ve gotten something out of it.

There’s been a lot to cover in this article, so I haven’t shown absolutely every change that needs to be made to get it going. Have a look at the networkFetch tag of my Android Examples GitHub Repository for the full source code.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Prexxi

    Hi. Thanks for the direction. I am trying to update time on textview every second and also update the twitter every 1 hour. I tried but couldn’t succeed. I tried to create two alarm managers, where one manager will start service every 1 hour and other manager will keep updating time every second. But still i am having hard time to achieve this. Please could give me some direction on how to approach this task ?. I want my widget to keep updating time plus fetching twitter every 2 or more hour since i wouldn’t be calling twitter service every second for obvious performance issue :D

    Thanks again.