For my first tutorial here at AndroidAppDev I’d like to start with a very useful battery widget to display current battery level.
I’m a new Android developer and I’ve been suffering a lot while learning how to develop widgets for this platform.
In my opinion, the currently available tutorials are really bad, and most of the time they don’t explain stuff, they just say, “do this and it will work.”
Frustrated by the lack of good tutorials, I decided to started a collection of tutorials myself.
This will be, hopefully, a good place for you to look for hints on how to code a widget or application for the open Android platform.
All of the code that I’ll develop for my tutorials will be open source, so that you can learn how to develop your own widgets. All of my code will be using GPLv3 license. This means that you can use all of my code, change it, redistribute it, sell it, as long as you keep your changes open source also. For details about this license please read about it here. This approach guarantees that everyone will benefit back from your changes and that they can still learn from them.
I’ll be assuming that you’re already a Java developer and that you’re familiar with the XML language and the Eclipse development environment, and also that you already have the Android SDK installed and ready to use. Google has a really good tutorial here on how to set up the SDK so it doesn’t make sense to waste time writing another.
If you want to learn how to write the usual “Hello, world!” program, please checkout Google’s tutorial here.
Our application will be called Batteroid, and we’ll start with version 0.1. This will be a really basic widget with a very basic interface and your feedback to this tutorial will guide me to teach you how to make it better.
The goal of this application, in this case, a widget, will be to just display a simple number. The difference between this widget and a simple “Hello, world” widget is that we’re not going to hard code the number, we’re going to get the number from the Android platform. Also, we need to be notified every time the battery level changes so, besides being very simple graphically, it will have a lot of concepts that will be helpful in future developments.
One of the most important files in an Android application or widget is the AndroidManifest.xml file. This is where we declare most of the stuff that our widget will need or use. For example, this is where we define the version number of our widget, the widget icon, the name that will appear in the widget list and the events that we want to listen to.
The main difference between applications and widgets is that a widget is like a small application that stays on your home screen and waits for things to happen. Usually they don’t get much interaction, they just display stuff. In our case, the current battery level. A widget also doesn’t appear in the applications list. You have to press your home screen until a list of options appears and choose the Widgets option to see the list of available widgets. Its really sad to see people rating widgets 1 star in the Android Market just because they can’t run it because they’re trying to find it in the applications list.
In this sense, a widget is like a receiver of events that are broadcasted by other widgets, applications or services and get updated accordingly. A widget itself extends the AppWidgetProvider class which extends BroadcastReceiver, so you can see that the class hierarchy reflects well the behaviour of this widgets.
We need to declare our widget in the AndroidManifest.xml file so that when we install the apk on our Android phone, the system will be able to list our widget name in the widget list.
Widgets are declared with the <receiver> tag, inside the <application> tag. Inside <receiver> we will have to declare the events we want to listen to, called intents, and also the meta information about our widget, for example, widget dimensions and the update interval.
We have to declare which Java class will represent our widget and this is defined in the android:name attribute.
<receiver android:name=".BatteroidAppWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/appwidget_info" />
</receiver>
The APPWIDGET_UPDATE intent is main intent for widgets and should always be declared. It is sent to your widget the first time you put the widget on your home screen and each time the update interval is reached (defined in the meta data xml, in this case the appwidget_info.xml file).
To create this appwidget_info.xml file you must create a folder called xml inside your res folder and create the xml file inside it. For this example it should look like this:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="72dip"
android:minHeight="72dip"
android:updatePeriodMillis="0"
android:initialLayout="@layout/main">
</appwidget-provider>
Lets have a look at the details of our BatteroidAppWidgetProvider class.
All Java classes that are supposed to represent widgets should extend the class AppWidgetProvider. From this class, we must override the onUpdate method to insert the functionality we want. In this case, show the battery level.
If we run the application with an empty class the code runs anyway. It opens the emulator, we press the home screen to insert a new widget. Our Batteroid shows up in the list and it shows “Hello World!” in the home screen.
Lets type in the code to register our intent to get the battery level on the onUpdate method, which we must override.
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
context.getApplicationContext().registerReceiver(this,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
ComponentName cn =
new ComponentName(context, BatteroidAppWidgetProvider.class);
appWidgetManager.updateAppWidget(cn, this.views);
}
The crucial line of code here is
context.getApplicationContext().registerReceiver(this,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
First of all, we can’t register the ACTION_BATTERY_CHANGED inside the manifest file because the documentation says:
You can not receive this through components declared in manifests, only by explicitly registering for it withContext.registerReceiver(). See ACTION_BATTERY_LOW, ACTION_BATTERY_OKAY, ACTION_POWER_CONNECTED, andACTION_POWER_DISCONNECTED for distinct battery-related broadcasts that are sent and can be received through manifest receivers.
Yes, I have lost a lot of time with that one, so I’m hoping that you won’t.
And, since registerReceiver is expecting a BroadcastReceiver and and AppWidgetProvider is already a BroadcastReceiver, you might be tempted to just write context.registerReceiver(this, new IntentFilter… but it won’t work either because you can’t register a receiver inside a BroadcastReceiver, that’s why you have to use the getApplicationContext method.
I have also lost A LOT of time on that one.
Let’s look now at our onReceive method:
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
Integer level = intent.getIntExtra("level", -1);
this.views.setTextViewText(R.id.battery_level, level.toString());
ComponentName cn =
new ComponentName(context, BatteroidAppWidgetProvider.class);
AppWidgetManager.getInstance(context).updateAppWidget(cn, this.views);
}
super.onReceive(context, intent);
}
The code is not complicated. We receive the intent and get the action associated with it. If the action is the action we’re expecting, in this case, an ACTION_BATTERY_CHANGED e get the integer extra stored inside the intent which has the level of the battery. With it we just set the TextView to display our number and update widget.
Here is the full code for our class:
package net.henriquerocha.android.batteroid;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.widget.RemoteViews;
public class BatteroidAppWidgetProvider extends AppWidgetProvider {
private RemoteViews views =
new RemoteViews("net.henriquerocha.android.batteroid", R.layout.main);
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
context.getApplicationContext().registerReceiver(this,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
ComponentName cn =
new ComponentName(context, BatteroidAppWidgetProvider.class);
appWidgetManager.updateAppWidget(cn, this.views);
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
Integer level = intent.getIntExtra("level", -1);
this.views.setTextViewText(R.id.battery_level, level.toString());
ComponentName cn =
new ComponentName(context, BatteroidAppWidgetProvider.class);
AppWidgetManager.getInstance(context).updateAppWidget(cn, this.views);
}
super.onReceive(context, intent);
}
}