Wednesday, June 29, 2011

Android Widgets- Tutorial


Lars Vogel

Version 1.2
01.09.2010 - 20.06.2011
Revision History
Revision 0.101.09.2010Lars Vogel
Created
Revision 0.2 - 1.213.12.2010 - 20.06.2011Lars Vogel
bug fixes and enhancements
Developing Android Widgets
This article describes how to create widgets in Android. It is based on Eclipse 3.6, Java 1.6 and Android 2.3.3 (Gingerbread).

1. Android Widgets

1.1. Widgets

Widgets are little applications which can be placed on the home screen of your Android device. An widget gets its data on a periodic timetable. There are two methods to update a widget, one is based on an XML configuration file and the other is based on AlarmManager. The XML configuration will wake up the device and can the smallest possible update interval is 30 minutes. The alarm manager allows you to be more resource efficient and to have a higher frequency of updates.
To create a App Widget you:
  • Define a layout file for your widget.
  • Maintain an XML file (AppWidgetProviderInfo) which describes the properties of the widget, e.g. size or fixed update frequency.
  • Create a broadcast receiver which will be used to update the widgets. This receiver extends AppWidgetProvider which provides additional lifecycle hooks for widgets.
  • Maintain the App Widget configuration in the "AndroidManifest.xml" file.
  • Optional you can also specify a configuration activities which is called once one instance of the widget is added to the homescreen.

1.2. Available views and layouts

A widget is restricted in the elements it can use. For layouts you can use "FrameLayout", "LinearLayout" and "RelativeLayout". As views you can use "AnalogClock", "Button", "Chromometer", "ImageButton", "ImageView", "ProgressBar" and "TextView".

1.3. Widget size

A widget will take a certain amount of cells on the homescreen. A cell is usually used to display the icon of one application. As a calculation rule you should define the size of the widget with the formula: ((Number of columns / rows)* 74)- 2. These are device independent pixels and the -2 is used to avoid rounding issues.

1.4. Widget updates and RemoteViews

To save power consumption a widget does not have its own process but is part of the homescreen process. Therefore the widget UI is created as a RemoteView. A RemoteView can be executed by another process with the same permissions as the original application.
In the widget configuration file you can specify a fixed update interval. The system will wake up after this time interval and call your broadcast receiver to udpate the widget. The smallest update interval is 180000 milliseconds (30 minutes). A more flexible update can be archived with the AlarmManager which we will demonstrate later in this tutorial.
Here you see an example configuration file for a widget.

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
 android:updatePeriodMillis="180000" 
 android:initialLayout="@layout/widget_layout"
 android:minHeight="72dp" android:minWidth="146dp">
</appwidget-provider>

   

1.5. AppWidgetProvider

Your broadcast receiver extends AppWidgetProvider. The AppWidgetProvider class implements the onReceive() method, extracts the required information and calls the following widget lifecycle methods.
As you can added several instances of a widget to the homescreen you have lifecycle methods which are called only for the first instance added / removed to the homescreen and others which are called for every instance of your widget.
Table 1. Lifecycle method
MethodDescription
onEnabled()Called the first time an instance of your widget is added to the homescreen
onDisabled()Called once the last instance of your widget is removed from the homescreen.
onUpdate()Called for every update of the widget. Contains the ID's of the widgets currently on the homescreen.
onDeleted()Widget instance is removed from the homescreen

All long running operations in these methods should be performed in a service, as the execution time for a broadcast receiver is limited. Also using asynchronous processing does not help as the system can kill the broadcast process after his onReceive() method.

1.6. Android Basics

The following assumes that you have already basic knowledge in Android development . Please check the Android development tutorial to learn the basics.

2. Example with fixed update interval

The following will create a simple example for a widget, we will create a widget which displays a random number and updates this number every 30 minutes. The resulting widget will look like the following.


Create a new Android project "de.vogella.android.widget.example" with no activity in the package "de.vogella.android.widget.example".
Create a new file "myshape.xml" under "/res/drawable-mdpi". This file will define the background we use in your widget.

<?xml version="1.0" encoding="UTF-8"?> 
<shape xmlns:android="http://schemas.android.com/apk/res/android" 
     android:shape="rectangle"> 
     <stroke android:width="2dp" android:color="#FFFFFFFF" />
     <gradient android:startColor="#DD000000" android:endColor="#DD2ECCFA" 
            android:angle="225"/> 

    <corners android:bottomRightRadius="7dp" android:bottomLeftRadius="7dp" 
     android:topLeftRadius="7dp" android:topRightRadius="7dp"/> 
</shape> 
  

Define the following "widget_layout.xml" file under res/layout.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent" android:layout_height="match_parent"
 android:background="@drawable/myshape" android:layout_margin="8dip">
 <TextView android:layout_margin="4dip" android:text="Static Text"
  android:id="@+id/TextView01" android:layout_width="match_parent"
  android:layout_height="match_parent" style="@android:style/TextAppearance.Medium"
  android:layout_gravity="center"></TextView>
</LinearLayout>

  

Create the "AppWidget Provider" Metadata file "widget_info.xml", via File -> New -> Android -> Android XML File.





<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
 android:updatePeriodMillis="300000" android:minWidth="300dp"
 android:minHeight="72dp" android:initialLayout="@layout/widget_layout">
</appwidget-provider>

  

Create the broadcast receiver which will be called for updates.

package de.vogella.android.widget.example;

import java.util.Random;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;

public class MyWidgetProvider extends AppWidgetProvider {

 @Override
 public void onUpdate(Context context, AppWidgetManager appWidgetManager,
   int[] appWidgetIds) {
  // Create some random data
  String fakeUpdate = "Random: ";
  Random random = new Random();
  int nextInt = random.nextInt(100);
  fakeUpdate += String.valueOf(nextInt);
  for (int i : appWidgetIds) {
   int widgetId = appWidgetIds[i];
   int number = (new Random().nextInt(100));

   RemoteViews views = new RemoteViews(context.getPackageName(),
     R.layout.widget_layout);
   views.setTextViewText(R.id.TextView01, String.valueOf(number));
   appWidgetManager.updateAppWidget(widgetId, views);
  }
 }
}

  

Open "AndroidManifest.xml" and maintain the following.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="de.vogella.android.widget.example"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
 <receiver android:name="MyWidgetProvider">
   <intent-filter>
    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
   </intent-filter>
   <meta-data android:name="android.appwidget.provider"
    android:resource="@xml/widget_info" />
  </receiver>
    </application>
    <uses-sdk android:minSdkVersion="8" />

</manifest> 
  

This attribute specifies that the AppWidgetProvider accepts the ACTION_APPWIDGET_UPDATE broadcast. This is the only broadcast that you must explicitly declare.
Run your app. Once your app has been deployed, long press on your desktop and install your new widget.



3. Update via a service and onClickListener

If you widget requires longer processing time you should do this processing in a service and perform the update of the widgets from their. The following will demonstrate the usage of a service. It will also show how to add a onClickListner to the widget. We will implement the onClickListener so that all widgets will be updated if one widget is selected.
First create a UpdateWidgetService Service in your existing project. For on introduction into services please see Android Service Tu torial

package de.vogella.android.widget.example;

import java.util.Random;

import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.widget.RemoteViews;

public class UpdateWidgetService extends Service {
 @Override
 public void onStart(Intent intent, int startId) {
  Log.i("UpdateWidgetService", "Called");
  // Create some random data
  String fakeUpdate = null;
  Random random = new Random();

  AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this
    .getApplicationContext());

  int[] appWidgetIds = intent
    .getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
  if (appWidgetIds.length > 0) {
   for (int widgetId : appWidgetIds) {
    int nextInt = random.nextInt(100);
    fakeUpdate = "Random: " + String.valueOf(nextInt);
    RemoteViews remoteViews = new RemoteViews(getPackageName(),
      R.layout.widget_layout);
    remoteViews.setTextViewText(R.id.TextView01, fakeUpdate);
    appWidgetManager.updateAppWidget(widgetId, remoteViews);
   }
   stopSelf();
  }
  super.onStart(intent, startId);
 }

 @Override
 public IBinder onBind(Intent intent) {
  return null;
 }
}

  

Change MyWidgetProvider to the following.

package de.vogella.android.widget.example;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;

public class MyWidgetProvider extends AppWidgetProvider {
 @Override
 public void onUpdate(Context context, AppWidgetManager appWidgetManager,
   int[] appWidgetIds) {

  // Build the intent to call the service
  RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
    R.layout.widget_layout);
  Intent intent = new Intent(context.getApplicationContext(),
    UpdateWidgetService.class);
  intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);

  // To react to a click we have to use a pending intent as the
  // onClickListener is
  // excecuted by the homescreen application
  PendingIntent pendingIntent = PendingIntent.getService(
    context.getApplicationContext(), 0, intent,
    PendingIntent.FLAG_UPDATE_CURRENT);
  remoteViews.setOnClickPendingIntent(R.id.layout, pendingIntent);

  // Finally update all widgets with the information about the click
  // listener
  appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);

  // Update the widgets via the service
  context.startService(intent);
 }
}

  

Once called this service will update all widgets. You can click on one of the widgets to update all widgets.

4. Thank you

No comments:

Post a Comment