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 notepad/to-do list 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

5 « Prev Page

11. Create a Settings screen

Settings screen Creating a Settings screen is easy with help of ADT.
Go to File > New > Other... and select Android Object within Android folder in the wizard. Click Next and select Settings Activity in listed templates. Settings Wizard Click Next, Next, and Finish.
All the necessary code will get generated in the project. Let's take a look at the modifications we've done to the SettingsActivity class.
	public class SettingsActivity extends PreferenceActivity {
		
		public static String TTS_PREF = "tts_pref";
		public static String ALPHABET_PREF = "alphabet_pref";
		public static String INTERVAL_PREF = "interval_pref";
		public static String DEFAULT_INTERVAL = "60";
		public static String ALL_ALPHABET = "*";	
		
		private static final boolean ALWAYS_SIMPLE_PREFS = true;
		
		@Override
		protected void onCreate(Bundle savedInstanceState) {
			super.onCreate(savedInstanceState);
			setTitle(getString(R.string.title_activity_settings));
			setupActionBar();
		}
		
		@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
		private void setupActionBar() {
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
				getActionBar().setDisplayHomeAsUpEnabled(false);
				getActionBar().setDisplayUseLogoEnabled(true);
			}
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
				getActionBar().setHomeButtonEnabled(false);
			}
		}	
		
		@Override
		protected void onPostCreate(Bundle savedInstanceState) {
			super.onPostCreate(savedInstanceState);

			setupSimplePreferencesScreen();
		}

		private void setupSimplePreferencesScreen() {
			if (!isSimplePreferences(this)) {
				return;
			}

			// In the simplified UI, fragments are not used at all and we instead
			// use the older PreferenceActivity APIs.

			// Add 'general' preferences.
			addPreferencesFromResource(R.xml.pref_general);
			
			// Bind the summaries of EditText/List/Dialog/Ringtone preferences
			// to their values. When their values change, their summaries are
			// updated to reflect the new value, per the Android Design
			// guidelines.
			bindPreferenceSummaryToValue(findPreference(TTS_PREF));
			bindPreferenceSummaryToValue(findPreference(ALPHABET_PREF));
			bindPreferenceSummaryToValue(findPreference(INTERVAL_PREF));		
		}	

		@Override
		public boolean onIsMultiPane() {
			return isXLargeTablet(this) && !isSimplePreferences(this);
		}

		private static boolean isXLargeTablet(Context context) {
			return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
		}

		private static boolean isSimplePreferences(Context context) {
			return ALWAYS_SIMPLE_PREFS
					|| Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
					|| !isXLargeTablet(context);
		}

		@Override
		@TargetApi(Build.VERSION_CODES.HONEYCOMB)
		public void onBuildHeaders(List<Header> target) {
			if (!isSimplePreferences(this)) {
				loadHeadersFromResource(R.xml.pref_headers, target);
			}
		}

		private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
			@Override
			public boolean onPreferenceChange(Preference preference, Object value) {
				String stringValue = value.toString();

				if (preference instanceof ListPreference) {
					// For list preferences, look up the correct display value in
					// the preference's 'entries' list.
					ListPreference listPreference = (ListPreference) preference;
					int index = listPreference.findIndexOfValue(stringValue);

					// Set the summary to reflect the new value.
					preference.setSummary(index >= 0 ? listPreference.getEntries()[index] : null);

				} else {
					// For all other preferences, set the summary to the value's
					// simple string representation.
					preference.setSummary(stringValue);
				}
				return true;
			}
		};

		private static void bindPreferenceSummaryToValue(Preference preference) {
			// Set the listener to watch for value changes.
			preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
			
			if (TTS_PREF.equals(preference.getKey())) return;

			// Trigger the listener immediately with the preference's
			// current value.
			sBindPreferenceSummaryToValueListener.onPreferenceChange(
					preference,
					PreferenceManager.getDefaultSharedPreferences(preference.getContext()).getString(preference.getKey(), ""));
		}
		
		//------------------------------------------------------------------------

		@TargetApi(Build.VERSION_CODES.HONEYCOMB)
		public static class GeneralPreferenceFragment extends PreferenceFragment {
			@Override
			public void onCreate(Bundle savedInstanceState) {
				super.onCreate(savedInstanceState);
				addPreferencesFromResource(R.xml.pref_general);

				// Bind the summaries of EditText/List/Dialog/Ringtone preferences
				// to their values. When their values change, their summaries are
				// updated to reflect the new value, per the Android Design
				// guidelines.
				bindPreferenceSummaryToValue(findPreference(TTS_PREF));
				bindPreferenceSummaryToValue(findPreference(ALPHABET_PREF));
				bindPreferenceSummaryToValue(findPreference(INTERVAL_PREF));
			}
		}	

	}
					
We have removed unnecessary preferences and have kept only General preferences for our simple requirement. There is a corresponding pref_general.xml file in res/xml directory that we've also modified.
	<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
		
		<Preference 
			android:summary="@string/app_desc" />    
		
		<ListPreference
			android:defaultValue="*"
			android:entries="@array/alphabets"
			android:entryValues="@array/alphabets"
			android:key="alphabet_pref"
			android:negativeButtonText="@null"
			android:positiveButtonText="@null"
			android:title="Show words beginning with" />    

		<ListPreference
			android:defaultValue="60"
			android:entries="@array/refresh_interval_titles"
			android:entryValues="@array/refresh_interval_values"
			android:key="interval_pref"
			android:negativeButtonText="@null"
			android:positiveButtonText="@null"
			android:title="Refresh interval" />
		
		<CheckBoxPreference
			android:defaultValue="false"
			android:key="tts_pref"
			android:summary="Listen to words and their meanings"
			android:title="Enable Text-to-speech" />    

	</PreferenceScreen>
						
Next, we will add code to detect TTS support before user can enable Text-to-Speech. We will use the code that we discussed in previous section.
Modify SettingsActivity class as follows.
	private static final int TTS_CHECK_CODE = 101;
	
	private static WeakReference<Activity> weakActivity;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		//...
		weakActivity = new WeakReference<Activity>(this);
	}	
	
	private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
		@Override
		public boolean onPreferenceChange(Preference preference, Object value) {
			String stringValue = value.toString();

			if (TTS_PREF.equals(preference.getKey())){
				if ("true".equalsIgnoreCase(value.toString())) {
					Intent checkIntent = new Intent();
					checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
					
					Activity activity = weakActivity != null ? weakActivity.get() : null;
					if (activity != null) {
						activity.startActivityForResult(checkIntent, TTS_CHECK_CODE);
					}
					return false;
				}
				
			}
			
			//...
			
			return true;
		}
	};

	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
	    if (requestCode == TTS_CHECK_CODE) {
	        if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
	            // success, create the TTS instance
	        	CheckBoxPreference cbPreference = (CheckBoxPreference) findPreference(TTS_PREF);
	        	cbPreference.setChecked(true);
	        	
	        } else {
	        	Toast.makeText(this, getString(R.string.setup_tts), Toast.LENGTH_LONG).show();
	        	
	            // missing data, install it
	            Intent installIntent = new Intent();
	            installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
	            startActivity(installIntent);
	        }
	    }
	}	
					
Basically, we allow user to enable TTS only if its supported on the device, otherwise direct him to the Play Store.

27. What's next?

We've learned to create a simple but useful app widget in this tutorial. You can read more about creating sophisticated widgets from the official Android developers site. You might find the following resources useful.
Share the love:  

More Stuff » 5

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