Monday, March 12, 2012

Creating An Android Widget

In this next post, I will discuss how I created a Widget for one of my apps that I am working on. This widget will launch an Activity when added to the home screen so the user can customize it. Also, the widget will have a button on it so that an action can be done right from the screen. The motivation for this is so a user can quickly do an action without needing to open the app up and navigate to the action desired.

First thing that will need to be done is to update the manifest file. The first thing to add is an activity named SelectExample. This will be called when the widget is added to the desktop. That is why the intent-filter android.appwidget.action.APPWIDGET_CONFIGURE is nested inside that activity definition.

Finally, a receiver needs to be added to know when the widget is updated. This is so the widget can be drawn correctly. The receiver is labelled WidgetExample below where the full manifest file is:



<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wetselsoftware.example.widgetexampleproject"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="7" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:label="@string/app_name"
            android:name=".WidgetExampleProjectActivity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
     
       
<activity android:name=".SelectExample">
 <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
 </intent-filter>
</activity>
<receiver android:name=".WidgetExample" android:label="Example">
 <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />                  </intent-filter>
 <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget1_info" />
</receiver>
    </application>

</manifest>
In the receiver block, you may notice the resource @xml/widget1_info.  This is very important.  You need to created a file in the res/xml folder and name it widget1_info.  Below is what the contents of mine look like.
The width and height are set so that the widget will be 2 blocks wide and 1 block tall.  The updatePeriodMillis is the number of milleseconds that will pass before Android will call the receiver for a chance to update the widget, if the value is less than 30 minutes, it is ignored.  Also, notice the android:configure value, this needs to point to the class that will handle the configuration of the widget when it is added to the home-screen.  The layout is defined in the widget1 file referenced in this XML and will be shown below.

res/widget1_info
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="147dp"
    android:minHeight="72dp"
    android:updatePeriodMillis="1000"
    android:configure="com.wetselsoftware.example.widgetexampleproject.SelectExample"
    android:initialLayout="@layout/widget1">
</appwidget-provider>



layout/widget1





<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="horizontal"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:gravity="center"
  android:layout_margin="4dp"
  android:background="@drawable/background">
  <LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
      <TextView
          android:id="@+id/widgetName"
          android:layout_gravity="left"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="TextView" />

  <Button
      android:id="@+id/widgetbutton"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="left"
      android:text="Launch"
      />
</LinearLayout>
 
</LinearLayout>

 OK, so that is the XML files.  Now, lets look at the WidgetExample receiver.  This class will extend the AppWidgetProvider class.  Specifically, the onUpdate() function needs to be implemented for the purposes of this demo.  This will update the GUI of the widget on the Home screen.  Basically in onUpdate, we loop through each widgetId, and for that ID grab the Name of the widget and use RemoteViews class to update the GUI dynamically.  In the SelectPet Activity which I will show next, it asks for a name for this Widget and I saved it in the SharedPrefernces and retrieved it in this class.
public class WidgetExample extends AppWidgetProvider {
  public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    final int N = appWidgetIds.length;
    Log.i("WidgetExample",  "Updating widgets " + Arrays.asList(appWidgetIds)); 
    for (int i = 0; i < N; i++) {
      int appWidgetId = appWidgetIds[i];
      Intent intent = new Intent(context, WidgetExampleProjectActivity.class);
      intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, (int)appWidgetIds[i]);
      Uri data = Uri.withAppendedPath(  Uri.parse("example" + "://widget/id/") ,String.valueOf(appWidgetId));
 intent.setData(data);
      PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

      RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget1);     
      String widgetName=PreferenceManager.getDefaultSharedPreferences(context).getString("widget"+(int)appWidgetIds[i],null);
      views.setTextViewText(R.id.widgetName,widgetName );
      views.setOnClickPendingIntent(R.id.widgetbutton, pendingIntent);
      appWidgetManager.updateAppWidget(appWidgetId, views);
    }
  }  
}
Finally, I will show the class SelectExample.  This class is called when the Widget is first added to the home screen.  Basically there is a simple GUI with a text field.  When the button is pressed, this class will save off the text in the TextField as the name to use for the widget.  Also, I go ahead and update the views in this class so the Widget is drawn correctly right away in case onUpdate is not called immediately.  It is important to set the result to RESULT_OK and return the widgetId in the result Intent.

package com.wetselsoftware.example.widgetexampleproject;


import android.app.Activity;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RemoteViews;

public class SelectExample extends Activity{

int mAppWidgetId=-1;
@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = getIntent();
        Bundle extras = intent.getExtras();
        if (extras != null) {
            mAppWidgetId = extras.getInt(
                    AppWidgetManager.EXTRA_APPWIDGET_ID, 
                    AppWidgetManager.INVALID_APPWIDGET_ID);
        }
    }
@Override
public void onResume(){
super.onResume();
setUpGUI();
}
public void setUpGUI(){
setContentView(R.layout.selectexample);
Button select=(Button)findViewById(R.id.button1);
select.setOnClickListener(new SelectHandler());
}
                
        private class SelectHandler implements View.OnClickListener
        {
        public void onClick(View v)
        {
        EditText et=(EditText)findViewById(R.id.editText1);
        String textName=et.getText().toString();
       
        //now save off name in the prefences
        SharedPreferences settings = PreferenceManager
.getDefaultSharedPreferences(getBaseContext());
        SharedPreferences.Editor editor = settings.edit();
editor.putString("widget"+mAppWidgetId, textName);
editor.commit();
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getBaseContext());
Intent intent = new Intent(getBaseContext(), WidgetExampleProjectActivity.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID , mAppWidgetId);
Uri data = Uri.withAppendedPath(  Uri.parse("example" + "://widget/id/") ,String.valueOf(mAppWidgetId));
     intent.setData(data);
   PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
   
RemoteViews views = new RemoteViews(getBaseContext().getPackageName(), R.layout.widget1);
views.setOnClickPendingIntent(R.id.widgetbutton, pendingIntent);
views.setTextViewText(R.id.widgetName, textName);
appWidgetManager.updateAppWidget(mAppWidgetId, views);
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
        }
        }                
}

 So, all the pieces are here now.  You can download this example project here.

No comments:

Post a Comment