Browse through more Android tutorials. If you'd like to see a tutorial on any particular topic, do leave a comment in the wishlist page. We frequently post new tutorials along with app releases. You may subscribe to our newsletter to get all updates in your inbox.
Now you can get the latest Java source bundled with each app update. Install the app from Google Play and go to Settings > Extras.
* Become a 10x programmer! →

«  Create an instant messaging app Create a currency converter app  »

Create an App Widget in Android with Text-to-Speech (TTS)

DownloadDownload
By

Keywords: AppWidgetProvider RemoteViews AppWidgetManager BroadcastReceiver Widget Configuration Activity AlarmManager TextToSpeech Service PreferenceActivity OnPreferenceChangeListener

Contents

2 « Prev Page

5. The Widget Provider class

The Widget Provider class is basically a BroadcastReceiver but the method we need to override is onUpdate(). Additionally, we can override onDeleted(), onEnabled(), and onDisabled() for receiving various callbacks.

You can learn more about using AppWidgetProvider class from the official docs.

We have modified the generated code (by ADT) as follows.
	public class VocabWidget extends AppWidgetProvider {
		
		public static final String ACTION_UPDATE = "com.appsrox.dailyvocab.action.UPDATE";
		
		@Override
		public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
			// There may be multiple widgets active, so update all of them
			final int N = appWidgetIds.length;
			for (int i = 0; i < N; i++) {
				updateAppWidget(context, appWidgetManager, appWidgetIds[i]);
			}
		}
		
		private void onUpdate(Context context) {
			AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

			ComponentName thisAppWidgetComponentName = new ComponentName(context.getPackageName(),getClass().getName());
			int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidgetComponentName);
			onUpdate(context, appWidgetManager, appWidgetIds);
		}	

		@Override
		public void onDeleted(Context context, int[] appWidgetIds) {
			// When the user deletes the widget
		}

		@Override
		public void onEnabled(Context context) {
			// Enter relevant functionality for when the first widget is created
			Util.scheduleUpdate(context);
		}

		@Override
		public void onDisabled(Context context) {
			// Enter relevant functionality for when the last widget is disabled
			Util.clearUpdate(context);
		}

		static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
			String[] content = getContent(context);

			// Construct the RemoteViews object
			RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.vocab_widget);
			
			views.setTextViewText(R.id.txtWord, content[0]);
			views.setTextViewText(R.id.txtMeaning, content[1]);
			
			views.setOnClickPendingIntent(R.id.btnNext, getPendingSelfIntent(context, ACTION_UPDATE));

			// Instruct the widget manager to update the widget
			appWidgetManager.updateAppWidget(appWidgetId, views);
		}
		
		private static PendingIntent getPendingSelfIntent(Context context, String action, String... content) {
			Intent intent = new Intent(context, VocabWidget.class);
			intent.setAction(action);
			return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
		}
		
		@Override
		public void onReceive(Context context, Intent intent) {
			if (ACTION_UPDATE.equals(intent.getAction())) {
				onUpdate(context);
			} else super.onReceive(context, intent);
		}    
		
	}
					
Essentially, we have taken control of widget update by scheduling an alarm when the widget gets enabled. And we have overridden onReceive() to handle the UPDATE broadcast triggered by the alarm.
The other modification we did is in updateAppWidget() method which is responsible for providing UI to the widget. Whenever the widget receives an update then onUpdate() is invoked which in turn calls updateAppWidget().

A widget runs as part of the host app process so we need RemoteViews for updating the UI and PendingIntent for performing any action.

6. The Widget layout

Now, let's take a look at the layout of the widget. Apart from text views, it contains buttons for speech functionality and for refreshing the widget manually.
Modify vocab_widget.xml in res/layout as follows.
	<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:padding="@dimen/widget_margin" >
		
		<RelativeLayout
			android:layout_width="match_parent"
			android:layout_height="match_parent"
			android:background="@drawable/scroll"
			android:padding="10dp" >
		
			<TextView
				android:id="@+id/txtWord"
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:layout_marginLeft="10dp"
				android:clickable="true"
				android:textSize="18sp"
				android:typeface="serif"
				android:textStyle="bold|italic"
				android:textColor="@android:color/black" />
			
			<ImageButton 
				android:id="@+id/btnSpeaker"
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:layout_toRightOf="@id/txtWord"
				android:layout_alignTop="@id/txtWord"
				android:paddingRight="2dp"
				android:paddingLeft="10dp"
				android:paddingTop="2dp"
				android:paddingBottom="10dp"
				android:background="@null"
				android:src="@drawable/speaker"
				android:visibility="gone" />
		
			<TextView
				android:id="@+id/txtMeaning"
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:layout_below="@id/txtWord"
				android:layout_alignLeft="@id/txtWord"
				android:layout_marginRight="10dp"
				android:textSize="12sp"
				android:typeface="sans"
				android:textColor="@android:color/black" />
		
			<ImageButton
				android:id="@+id/btnNext"
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:paddingRight="2dp"
				android:paddingLeft="10dp"
				android:paddingTop="10dp"
				android:paddingBottom="2dp"
				android:layout_alignParentRight="true"
				android:layout_alignParentBottom="true"
				android:background="@null"
				android:src="@drawable/next" />
		
		</RelativeLayout>    

	</FrameLayout>
						

We can only use a limited set of widgets and layouts for the widget UI as listed here.

7. Using AlarmManager to update App Widget

Recall that in widget provider class we used onEnabled() and onDisabled() callback methods for scheduling and clearing the alarm through utility methods. Here is the code for Util class.
	public class Util {

		public static void scheduleUpdate(Context context) {
			SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
			String interval = prefs.getString(SettingsActivity.INTERVAL_PREF, null);
			
			AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
			long intervalMillis = Integer.parseInt(interval)*60*1000;

			PendingIntent pi = getAlarmIntent(context);
			am.cancel(pi);
			am.setInexactRepeating(AlarmManager.RTC, System.currentTimeMillis(), intervalMillis, pi);
		}
		
		private static PendingIntent getAlarmIntent(Context context) {
			Intent intent = new Intent(context, VocabWidget.class);
			intent.setAction(VocabWidget.ACTION_UPDATE);
			PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
			return pi;
		}

		public static void clearUpdate(Context context) {
			AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
			am.cancel(getAlarmIntent(context));
		}	
		
	}
					
We get the refresh interval provided by the user from shared preferences and use it for creating inexact repeating alarms using AlarmManager.
Also we need to reschedule the alarms since they are lost on reboot. So we create a broadcast receiver for boot completed and register it in the manifest.
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application>
        <!-- ... -->

		<receiver android:name=".BootReceiver">
		    <intent-filter>
		        <action android:name="android.intent.action.BOOT_COMPLETED"/>
		    </intent-filter>
		</receiver>        
    </application>
						
The BootReceiver simply reschedules the alarms if the widget was added to the Home screen.
	public class BootReceiver extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {
			AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
			int[] ids = appWidgetManager.getAppWidgetIds(new ComponentName(context, VocabWidget.class));
			if (ids.length > 0) {
				Util.scheduleUpdate(context);			
			}
		}

	}
					
Share the love:  

Next Page » 2

App Gen
App Name:
Project Name:
Package:
Screens:
Splash
Login
Help
Main
List  Grid  Pager
Detail
Settings
Options:
Action Bar
Navigation Drawer
Dummy Data
Generate
Free Apps