Android Howto-Hellowidget
Android Howto-Hellowidget
Table of Contents
Document history......................................................................................................1
HelloWidget...............................................................................................................1
Prerequisites.........................................................................................................1
Let’s start from scratch..........................................................................................1
The Java-way Time-Widget..................................................................................6
The Service-way Time-Widget (not working with 1.6 any more)..........................8
How to use Buttons on Widgets................................................................................9
Prerequisites.........................................................................................................9
The Layout............................................................................................................9
AppWidget Provider............................................................................................10
Manifest file.........................................................................................................10
ButtonWidget Provider........................................................................................11
Document history
Version Date User Description
1.0 2009-07-11 Norbert Möhring Initial document.
moehring.n [at] googlemail.com
1.1 2009-07-21 Norbert Möhring Small fixes in code (copy&paste erros ).
moehring.n [at] googlemail.com Thanks to Andreas Kompanez ([email protected])
for the review.
Better layout.
Added document history and table of contents.
Put all links behind text → looks better
1.2 2009-07-23 Norbert Möhring Fixed some typos
moehring.n [at] googlemail.com
1.3 2009-09-27 Norbert Möhring - Some comments to the new 1.6 Version
moehring.n [at] googlemail.com - How to use Buttons on widgets
HelloWidget
Since there is only the one not that self explaining example of a widget I
decided to invest some nightly hours to cut that example into pieces and
then start from scratch with an easy to follow “Hello Widget” tutorial.
Prerequisites
You should already have the android SDK and android Eclipse IDE plug-in
installed and running. If not, go here to learn how to get started with the
android plug-in. Also you should have at least basic knowledge about Java
programming since this is not a Java tutorial.
In Eclipse, go to
The project name will be “Hello Widget” and in this case the target platform will be ‘Android 1.5’.
Uncheck the probably already checked Box “Create Activity”. We won’t create an Activity here we just
want a simple widget.
The project wizard gave us some default stuff already, like the default android app-icon e.g.
We’ll start with the layout and design of our widget.
Open main.xml and modify it like this:
<TextView android:id="@+id/widget_textview"
android:text="@string/widget_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center_horizontal|center"
android:layout_marginTop="5dip"
android:padding="10dip"
android:textColor="@android:color/black"/>
</LinearLayout>
That’s because you don’t have the widget_bg_normal resource at this point. I borrowed the image
from the SimpleWiktionary widget. It’s a png NinePatch image which will be our background of the
widget.
Put the file widget_bg_normal.9.png (or your own background image) into the folder res/drawable.
You also don’t have the @string/widget_text resource. To fix that, go to res/values/string.xml and add this
line to it:
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>
The meta-tag tells android about your widget provider. In this case, the widget provider is located at
res/xml/hello_widget_provider.xml. The provider.xml is pretty much self explaining:
Define the size of your widget (should match the guidelines for desktop widgets).
updatePerdiodMillis as it’s name already says, the time in milliseconds to update your widget. In our
case, this is unimportant and we don’t need it because we don’t do any update on our widget.
The initial layout points the the main.xml file. We already defined our layout and design.
The only thing that is missing to run our widget is the Class that extends the AppWidgetProvider
which we declared in AndroidManifest.xml at the receiver-tag.
package de.thesmile.android.widget;
import android.appwidget.AppWidgetProvider;
Since our Widget is doing nothing, you’re now ready to go. To start your application, right click on your
project → Run As → Android Application
This will start the Android Virtual Device (AVD) and install your widget on this device.
Ok, that was pretty easy and now you have a widget which can do …. nothing
Let’s do something with our widget ... display the current time. I’ll show you two versions of the time
example, one that I would call the Java-way and the other one to introduce you to services in widgets
(Service-way).
This is how the Java-way Time-Widget will look like. It will update
every second and display the time.
By the way: It’s 11:50 pm right now, I don’t know where the AVD
gets it’s time from but it’s definitely not my system time.
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
RemoteViews remoteViews;
AppWidgetManager appWidgetManager;
ComponentName thisWidget;
DateFormat format = SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM,
Locale.getDefault());
@Override
public void run() {
remoteViews.setTextViewText(R.id.widget_textview,
"Time = " + format.format(new Date()));
appWidgetManager.updateAppWidget(thisWidget, remoteViews);
}
}
}
The method onUpdate will be called at first. We create a timer that will run our MyTime-Thread every
second. The constructor of the MyTime-Class gets some information to update our widget on the
desktop.
Get our main view that we created (at that point you could also change your layout to another design).
remoteViews = new RemoteViews(context.getPackageName(), R.layout.main);
In the run method, we set the time with our new Date and update the remote View.
That’s already it. Now you have a widget that is displaying the current time.
At that point I want to give you some further information about a bug that has not been fixed yet, either
in the AVD nor on any machine. The bug-fix didn’t make it into the CUPCAKE Version (1.5).
To delete a widget, you tab and hold, then drag it and drop it into the trash can at the bottom.
At that point the widget provider usually runs an onDelete and onDisable method. Currently this is not
happening since the wrong IDs are sent. To fix this problem and really delete your widget, add the
following code to the onReceive method:
@Override
public void onReceive(Context context, Intent intent) {
onReceive is always called before onUpdate is called. This code snipped checks the called action
and decides weather to just run the receive method or the delete method.
Jeff Sharkey provided us with this useful code-snipped.
If you don’t do that, your widget won’t be displayed any more, but your widget provider will still run
onReceive and onUpdate in the given period (updatePerdiodMillis) which will eat up battery and
memory. It seems like this problem has been fixed in the 1.6 update so that you don't need this
additional code anymore.
Usually your widget will do some more stuff than updating your time. To do so, you should create an
UpdateService.class. Here’s the Time-Widget with a service. This is just an example and I’m
pretty sure that the service-way of the time widget will eat up more battery than the Java-way.
To register and start the service, change your onUpdate method like this:
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
@Override
public void onStart(Intent intent, int startId) {
RemoteViews updateViews = new RemoteViews(this.getPackageName(),
R.layout.main);
Date date = new Date();
DateFormat format = SimpleDateFormat.getTimeInstance(
SimpleDateFormat.MEDIUM, Locale.getDefault());
If you want the time to be updated every second, your have to modify the hello_widget_provider.xml
file and set updatePerdiodMillis to 1000.
The android update 1.6 unfortunately does not support any update periode that is less then 30
minutes. The result is, that you can not run the “service-way” in 1.6 any more. Since updating every
second is not very useful concerning the battery life of your phone, the 30 minute minimum period
makes sense. Still, it is not very useful concerning development and testing because no developer
wants to wait for 30 minutes to test the update of his widget.
Anyway, for usability purposes one should always use an AlarmManager for widget updates, since you
can let the user configure the periode. I'll get to the AlarmManager in my next tutorial update.
Prerequisites
For the prerequisites, you already know how to create a widget project
from scratch. Just do that, or download the source code for this
example widget. You can get it here.
The Layout
For the layout, we use the main.xml which will look like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:orientation="horizontal"
android:background="@drawable/widget_bg_normal"
android:layout_gravity="center"
android:layout_height="wrap_content">
<Button android:text="B1" android:id="@+id/button_one"
android:layout_gravity="center_horizontal|center"
android:layout_height="fill_parent"
android:layout_width="wrap_content"/>
We use the background from our first widget and add two buttons to it, that's all we need.
Since this example will also start an activity from a button, we have to create a layout for this activity.
This e.g. could be a configuration screen. The configure.xml file looks like this:
<TableLayout android:id="@+id/TableLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/TextOne"
android:text="Text"
android:textColor="@android:color/white"
android:textSize="15dip"
android:textStyle="bold"
android:layout_marginTop="5dip"/>
<EditText android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/txtFieldId"
android:clickable="true"
android:inputType="number"
android:layout_marginTop="5dip"
android:layout_marginBottom="20dip"/>
</TableLayout>
We just added a label with the string “Text” and a textfield to the screen. Nothing will be functional
here.
The appwidget-provider xml named button_widget_provider.xml is very simple. We don't need any
update periode so it will look like this:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="146dip"
android:minHeight="72dip"
android:updatePeriodMillis="0"
android:initialLayout="@layout/main"
/>
Manifest file
The manifest file will have some more information this time and should look like this:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.thesmile.android.widget.buttons"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<!-- this activity will be called, when we fire our self created ACTION_WIDGET_CONFIGURE -->
<activity android:name=".ClickOneActivity">
<intent-filter>
<action
android:name="de.thesmile.android.widget.buttons.ButtonWidget.ACTION_WIDGET_CONFIGURE"/>
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>
ButtonWidget Provider
First of all, we configure our own events which we already used in our manifest file. You should always
make sure that your actions have unique names not only in the namespace of your own apps but in
the global namespace of the whole android framework. My idea is to just use your package name of
the application to insure that. (I didn't do that in this example)
public class ButtonWidget extends AppWidgetProvider {
[...]
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
remoteViews.setOnClickPendingIntent(R.id.button_one, actionPendingIntent);
remoteViews.setOnClickPendingIntent(R.id.button_two, configPendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
}
First, we get the RemoteViews for our main layout; that's where we will be working on. Then we
configure a new Intent that will hold our configuration activity. This event will be given our action
ACTION_WIDGET_CONFIGURE.
The second Intent will be configured with our ButtonWidget and be given the action
ACTION_WIDGET_RECEIVER. We also give this action some extra data that we will use later to display
something on the screen. This is just to show you how you can add extra data to an intent and use it
later when this intent is captured by whatever application is catching it.
As usual, we have to update our widget with the appWidgetManager to „commit“ the updates. Now
everything is registered to our widget and the button „B2“ will be working already since it will start our
Activity as soon as you click the button.
Button one will do nothing at the moment because we didn't tell our ButtonWidget to do anything when
it receives the action ACTION_WIDGET_RECEIVER.
@Override
public void onReceive(Context context, Intent intent) {
}
super.onReceive(context, intent);
}
}
At the beginning I put the 1.5 onReceive-fix here, but this time we ask for our action in the else
statement before we call super.onReceive. If ACTION_WIDGET_RECEIVER is called, we get our extra data
from the intent (which was stored under the key „msg“) and display a Toast-Message with this string to
the user.
As a little gimmick I also show you how you use the NotificationManager of the android framework.
The message „Button 1 clicked“ will pop up on the notification bar (the bar where your clock is placed)
and will also show the widget icon.
Feedback appreciated