Jeff Sharkey



Quick database row editor in Android 0.9 SDK

For several Android projects I’ve needed a quick way of editing database rows without building an entire GUI. In the 0.9 SDK, we saw the introduction of the Preferences framework for storing simple application data, along with the PreferenceActivity family of classes for rapidly creating editable GUIs with almost zero effort. Wouldn’t it be awesome if we could edit database rows just as easily?

It is possible if you’re willing to do a little hacking. Essentially we’re providing a fake SharedPreferences up to a PreferenceActivity window. Instead of reading and saving from the application preferences, our SharedPreferences class will be using a specific database row for its storage. To make it all work, we then override the PreferenceActivity.getSharedPreferences() method to return our fake SharedPreferences instance.

Below is some example code of this approach being used in ConnectBot, an Android SSH client that I’ve been working on. First the host_prefs.xml file where we define the “preferences” that will be written to our database. Notice that I’m setting android:key to the database column names, which will make pairing the data up much easier later.

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
	<EditTextPreference
		android:key="nickname"
		android:title="Nickname"
		/>
	<ListPreference
		android:key="color"
		android:title="Color category"
		android:entries="@array/list_colors"
		android:entryValues="@array/list_colors"
		/>
	<CheckBoxPreference
		android:key="usekeys"
		android:title="Use SSH keys"
		/>
	<PreferenceCategory android:title="Connection settings">
		<EditTextPreference
			android:key="username"
			android:title="Username"
			/>
		<EditTextPreference
			android:key="hostname"
			android:title="Host"
			/>
		<EditTextPreference
			android:key="port"
			android:title="Port"
			/>
	</PreferenceCategory>
</PreferenceScreen>

Now let’s look at the code we’ll be using for our PreferenceActivity. Nothing too special except we are overriding the getSharedPreferences(), which is how the PreferenceActivity will get its data source. In this example we’re doing a quick hack by passing the exact _id into the onCreate() as Intent.EXTRA_TITLE. Another way to handle this would be to pass around ContentProvider URIs, which would help clean up this code in several places.

With that approach, the best way to create the SharedPreferences instance would be by passing the URI as the name string to the getSharedPreferences() call. We could easily set the name string our PreferenceActivity requests by calling getPreferenceManager().setSharedPreferencesName(uri.toString()).

public class HostEditor extends PreferenceActivity
	implements OnSharedPreferenceChangeListener {
	protected CursorPreferenceHack pref;

	@Override
	public SharedPreferences getSharedPreferences(String name, int mode) {
		Log.d(this.getClass().toString(), String.format("getSharedPreferences(name=%s)", name));
		return this.pref;
	}
	
	@Override
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		
		HostDatabase db = new HostDatabase(this);
		int id = this.getIntent().getIntExtra(Intent.EXTRA_TITLE, -1);
		
		// TODO: we could pass through a specific ContentProvider uri here
		//this.getPreferenceManager().setSharedPreferencesName(uri);
		
		this.pref = new CursorPreferenceHack(db.getWritableDatabase(), db.TABLE_HOSTS, id);
		this.pref.registerOnSharedPreferenceChangeListener(this);
		
		this.addPreferencesFromResource(R.xml.host_prefs);
		this.updateSummaries();
	}
	
	public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
		// update values on changed preference
		this.updateSummaries();	
	}

	protected void updateSummaries() {
		// for all text preferences, set summary as current database value
		for(String key : this.pref.values.keySet()) {
			Preference pref = this.findPreference(key);
			if(pref == null) continue;
			if(pref instanceof CheckBoxPreference) continue;
			pref.setSummary(this.pref.getString(key, ""));
		}
	}
}

And finally the CursorPreferenceHack source. It’s pretty simple, and just wraps the SharedPreferences calls as needed when converting them over into Cursor calls. The resulting GUI is easy to change, just remember to set your android:key values to database column names so they can be resolved correctly:

So there you have it, this approach allows you to make rapid GUI interfaces to safely update your database-backed information. It’s definitely a hack for now, but it makes these GUI interfaces a snap. I think the Preferences framework itself might use databases for its normal storage, so they might generalize this family of classes in the future.