Chapter 7. Preferences, File System, Options Menu, and Intents
| If you're new to the Android mobile operating system, Learning Android is the perfect way to master the fundamentals. This gentle introduction shows you how to use Android's basic building blocks to develop user interfaces, store data, and more. Buy the print book or ebook. |
In this chapter, you will learn how to create preferences for your application, how the file system is organized, and how to use intents and the options menu to jump from one activity to another.
Preferences
Preferences are user-specific settings for an application. Preferences usually consist of some configuration data as well as a user interface to manipulate that data.
From user interface point of view preferences can be simple text values, checkboxes, selections from a pull-down menu, or similar. From data point of view, preferences is a collection of name-value pairs, also known as key-value or attribute-value pairs. The values are basic data types, such as integers, booleans, strings, and similar.
Our micro-blogging applications needs to connect to specific server in the cloud using specific user account information. For that, Yamba needs to know username and password for that account as well as the URL of the server it’s connecting to. This URL is also knowns as API root. So, in our case, we’ll have three fields where user can enter and edit username, password and API root. This data will be stored as strings.
To enable our app to handle user-specific preferences, we’d have to build a screen to enter the information, a Java code to validate and process that information, and some kind of storage mechanism to store this information.
While all this sounds like a lot of work, Android provides a framework to help streamline working with user preferences. First, we’ll define what our preference data looks like in a Preference resource file.
The steps to creating preferences for our application will be to:
-
Create Preference resource file
prefs.xml. -
Implement
PrefsActivity.javafile that inflates that resource file. -
Register this new activity with the
AndroidManifest.xmlfile. - Provide a way to start that activity from the rest of the application.
Prefs Resource
We are going to start by creating the prefs.xml - a resource file that outlines what our preference screen will look like. The easiest way to create it would be to use New Android XML File tool in Eclipse. To start the New Android XML File dialog, go to File→New→Android XML File, or click on the little a+ icon in the top menu bar of Eclipse: 
The key is to give the new file the name, in this case prefs.xml and to choose Preference for the type of resource. The tool should automatically suggest that this new file should be created in /res/xml folder and that the root element for the XML file should be PreferenceScreen. Just like discussed before in the section called “Alternative Resources”, we could create alternative versions of this same resource by applying various qualifiers such as screen size and orientation, language and region, etc.
Note
We’re using Eclipse tools where applicable to get the job done quicker. If you were to use another tool, you’d have to create this file manually and put it in the right folder.
Once you click on Finish, Eclipse will create a new file for you and open it up. Eclipse typically opens the XML files it knows about in its developer-friendly view.
In this view, you can create the username preference entry by selecting PreferenceScreen on the left, and then choosing Add→EditTextPreference On the right hand side, expand Attributes from Preferences section. Eclipse will offer you number of attributes to set for this EditTextPreference.
Not all attributes are equally important. Typically, you will care about the following:
- Key
- A unique identifier for each preference item. This is how we’ll look up particular preference later.
- Title
- The name of this preference that user will see. It should be a short name that fits on a single line of the preference screen.
- Summary
- A short description of this preference item. This is optional but highly recommended.
For username preference, we’ll put username for key. We will define Title and Summary in strings.xml as this is the best practice.
Instead of modifying strings.xml file directly, you can use an Eclipse shortcut. Here’s how it goes:
- Click on Browse and select New String…. This will open a dialog to create a new string resource.
-
Enter
titleUsernamefor R.string. value and Username for String value. -
Click Ok and this will insert a new string resource in
strings.xmlresource. - You can now pick that value from the list of resources.
The above describes how to add Username preference item. You can now repeat the same steps for Password and API Root items.
To switch to the actual XML code by clicking on the tab on the bottom of this window:
The raw XML for the preference resource looks like this:
Example 7.1. res/xml/prefs.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference android:title="@string/titleUsername"
android:summary="@string/summaryUsername" android:key="username"></EditTextPreference>
<EditTextPreference android:title="@string/titlePassword"
android:password="true" android:summary="@string/summaryPassword"
android:key="password"></EditTextPreference>
<EditTextPreference android:title="@string/titleApiRoot"
android:summary="@string/summaryApiRoot" android:key="apiRoot"></EditTextPreference>
</PreferenceScreen><PreferenceScreen> is the root element that defines our main preference screen. It has three children, all <EditTextPreference>. This is simply a piece of editable text. Other common elements here could be <CheckBoxPreference>, <ListPreference>, and so on.
The main property of any of these elements is the key. The key is how we’ll look up these values later on. Remember, preferences is just a set of name-value pairs at the end of the day.
Like we said couple of times earlier, while Eclipse does provide a developer-friendly tools to manage XML files, you often run into certain limitations with with Eclipse. For example, we would like to hide the actual text that the user types in the password field as it’s commonly done with passwords. Android does provide support for that but Eclipse tools haven’t integrated it in there yet. Since we can always edit the XML directly, in this case we to that to add android:password="true" property to our password property. This will cause the password to be masked while user is typing it in.
PrefsActivity
Now that we have the preferences defined in their own XML resource file, we can create the activity to display these preferences. You may recall from <<Activities> that every screen in an Android app is an activity. So, to display the screen where user enters username and password for their online account, we’ll create an activity to handle that screen. This activity will be a special preference-aware activity.
To create an activity, we create a new Java class. To do so, in Eclipse, select your package under your src folder, right-click on the package and select New→Class. A New Java Class window will pop up. You just need to enter PrefsActivity for the Name and click Finish. This will create PrefsActivity.java file under your package in your source folder.
Our PrefsActivity class going to be a very simple Java file. This is because we inherit from PreferenceActivity, an Android framework provided class that knows how to handle preferences.
Example 7.2. PrefsActivity.java
package com.marakana.yamba2;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class PrefsActivity extends PreferenceActivity { //
@Override
protected void onCreate(Bundle savedInstanceState) { //
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.prefs); //
}
}
Unlike regular activities, PrefsActivity will subclass (i.e. extend) PreferenceActivity class.
| |
Just like any other activity, we override onCreate() method to initialize the activity.
| |
Unlike regular activities that usually call setContentView(), preference activity will set its content from the prefs.xml file via a call to addPreferencesFromResource().
|
Tip
If you didn’t want to type the long signature of onCreate() and other methods that we often have to implement or override, you could use an Eclipse tool to help you with that. While in your PrefsActivity.java and after you have added ... extends PreferenceActivity..., you can choose Source→Override/Implement Methods…. This will bring up a dialog box with appropriate selection of methods you could override or implement given that you are subclassing PreferenceActivity class. In here, you can choose onCreate() and Eclipse will insert the stub for this method into your code.
Update Manifest File
Whenever we create one of these main building blocks (Activities, Services, Broadcast Receivers, Content Providers) we need to define them in AndroidManifest.xml file. In this case, we have a new PrefsActivity and we must add it to the manifest file.
Just like with any Android XML file, opening AndroidManifest.xml in Eclipse will typically bring up the developer-friendly view of that file. In this file, you could choose Application tab, then under Application Nodes choose Add→Activity and for its Name type .PrefsActivity.
However, we can also do this straight from the raw XML by clicking on AndroidManifest.xml tab on the bottom of this window. I find that while Eclipse is useful when it comes to creating XML files, often editing raw XML is faster and gives you much more control.
Tip
When editing code in Eclipse, you can use Ctrl+Space key shortcut to invoke type-ahead feature of Eclipse. This is every useful both for XML and Java code and is context-sensitive, meaning Eclipse is smart enough to know what could possibly be entered at that point in code. Using Ctrl+Space makes your life as programmer much easier as you don’t have to remember long method names and tags, plus it helps avoid typos.
So after adding our manifest file now looks like this:
Example 7.3. AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1" android:versionName="1.0" package="com.marakana.yamba2">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".StatusActivity" android:label="@string/titleStatus">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".PrefsActivity" android:label="@string/titlePrefs" /> <!--
-->
</application>
<uses-sdk android:minSdkVersion="4" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>We now have a new preference activity, but no good way of getting to it yet. We need a way to launch this new activity. For that, we use options menu.
Options Menu
To add support for options menu to an application, we need to do the following:
-
Create
menu.xmlresource where we specify what the menu consists of. -
Add
onCreateOptionsMenu()to the activity that we want to provide this menu. This is where we inflate themenu.xmlresource. -
Provide handling of menu events in
onOptionsItemSelected().
Menu Resource
In this view, you can click on Add→Item. This will add a new menu item to your menu. In the Attributes section on the right, you can see over a dozen various attributes that we can set for this menu item. Just like before, not all attributes are equally important.
- Id
-
The unique identifier of this resource. Just as when we designed the layout in Chapter 6, Android User Interface, this identifier is typically of form
@+id/someIdwhere someId is the name that you give it. This name should contain only letters, numbers and underscore. - Title
-
The tile of this menu as it will appear when displayed. Keep in mind that space on the screen is typically limited so keep the title short. Alternatively, you can provide Title condensed attribute for shorter version of the title that will be shown instead of Title if space is limited. Just like before, best practice is to define the actual text value of the title in
strings.xmlresource and just reference it here. - Icon
- The icon that shows along with the menu item’s title. While is is not required, it is a very useful visual cue from usability point of view. However, in this case it illustrates how to point to Android system resources.
The next section describes these resources in more detail.
Android System Resources
Just like your application can have its resources so can Android system as well. Like most other operating systems, Android comes with some preloaded images, graphics, sound clips, and other types of resources. Recall that our app resources are in /res/. To refer to Android system resources prefix them with with android: keyword in XML, for example @android:drawable/ic_menu_preferences. If you are referring to an Android system resource from Java, then you use android.R instead the usual R reference.
Tip
The actual resource files are in your SDK, inside specific platform folder. For example, if you are using Android 9 (Gingerbread), the location of the resource folder would be android-sdk/platforms/android-9/data/res/.
Example 7.4. res/menu/menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/itemPrefs" android:title="@string/titlePrefs"
android:icon="@android:drawable/ic_menu_preferences"></item>
</menu>As you can see, there’s just one <item> element within our <menu> element making this a single menu item menu.
Update StatusActivity to Load Menu
We need to update the StatusActivity to load up the options menu. To do that, add onCreateOptionsMenu() method to StatusActivity. This method gets called only first time when user clicks on the menu button.
// Called first time user clicks on the menu button
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater(); //
inflater.inflate(R.menu.menu, menu); //
return true; //
}Update StatusActivity to Handle Menu Events
We also need a way to handle various clicks on the menu items. To do that, we add another callback method, onOptionsItemSelected(). This method is called every time user clicks on a menu item.
// Called when an options item is clicked
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { //
case R.id.itemPrefs:
startActivity(new Intent(this, PrefsActivity.class)); //
break;
}
return true; //
}| Since the same method is called regardless which item user clicked on, we need to figure out the id of that item and based on that switch to a specific case to handle each item. At this point, we only have one menu item, but that might change in the future. Switching on item ID is a very scalable approach and will adapt nicely as our application grows in complexity. | |
startActivity() method in context allows us to launch a new activity. In this case, we are creating a new intent specifying to start PrefsActivity class.
| |
Return true to consume the event here.
|
Tip
Just like before, you could use Eclipse shortcut Source→Override/Implement Methods to add both onCreateOptionsMenu() and onOptionsItemSelected().
Strings Resource
Our updated strings.xml now looks like this:
Example 7.5. res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Yamba 2</string> <string name="titleYamba">Yamba 2</string> <string name="hintText">Please enter your 140-character status</string> <string name="buttonUpdate">Update</string> <string name="titleStatus">Status Update</string> <string name="titlePrefs">Prefs</string> <string name="titleUsername">Username</string> <string name="titlePassword">Password</string> <string name="titleApiRoot">API Root</string> <string name="summaryUsername">Please enter your username</string> <string name="summaryPassword">Please enter your password</string> <string name="summaryApiRoot">URL of Root API for your service</string> </resources>
You should be able to run your application at this point and and see the new PrefsActivity by clicking on Menu→Prefs in StatusActivity. Try changing your username and password, then reboot your phone, restart the app, and verify that the information is still there.
Shared Preferences
Now that we have Preference Activity, and a way to save our username, password and the API root, it is time to make use of it. To programmatically access your preferences, we’ll use SharedPreference class provided by the Android framework.
The fact that this class is called SharedPreference refers to the fact that this preference is easily accessible from anywhere in this application. So, any component of an Android application, such as Activities, Services, Broadcast Receivers, and Content Providers.
In StatusActivity, add definition for prefs object globally to the class:
SharedPreferences prefs;
Now, to get the preference object, add the following to onCreate():
@Override
public void onCreate(Bundle savedInstanceState) {
...
// Setup preferences
prefs = PreferenceManager.getDefaultSharedPreferences(this); //
prefs.registerOnSharedPreferenceChangeListener(this); //
}
Each application has its own shared preferences that all components of this application context share. To get the instance of this SharedPreferences, we use PreferenceManager.getDefaultSharedPreferences() and pass it this as the current context for this app. The name "shared" could be confusing - it means that this preference object contains data that is shared by various parts of this application only. It doesn’t mean any of this data is shared with any other application.
| |
Preferences can and do change by the user. So we need a mechanism to notify this activity that old values are stale. To do that, we register this, meaning our StatusActivity with our shared preferences. For this to work, we’ll need to add ...implements OnSharedPreferenceChangeListener to our class definition as well as implement the required onSharedPreferenceChanged() method. This method will be explained in a bit.
|
Now that we have username, password and API root coming from user-defined preferences, we can refactor our twitter code so it no longer hard-codes them. To do that, we add a private method to StatusActivity that is responsible for returning valid twitter object. This method lazily initializes twitter meaning if twitter exists it returns it as-is, otherwise creates it.
private Twitter getTwitter() {
if (twitter == null) { //
String username, password, apiRoot;
username = prefs.getString("username", ""); //
password = prefs.getString("password", "");
apiRoot = prefs.getString("apiRoot", "http://yamba.marakana.com/api");
// Connect to twitter.com
twitter = new Twitter(username, password); //
twitter.setAPIRootUrl(apiRoot); //
}
return twitter;
}| Only if twitter is null, i.e. undefined, we create it. | |
Get the username and password from the shared preference object. The first parameter in getString() is the key we assigned to each preference item, such as username and password. The second argument is the default value in case such preference is not found. Keep in mind that first time user runs your application, the preference file doesn’t exist so defaults will be used. So, if user hasn’t went to PrefsActivity to setup her preferences, this code will attempt to login with empty username and password, and thus fail. The failure however is going to happen when try try to do the actual status update because that’s how jtwitter library is designed.
| |
| We login to twitter service with user-defined preferences. | |
| Remember that we need to update the actual service that we using by updating the API root URL for that service. |
Now, we don’t use twitter object directly any more, but call getTwitter() to get it instead. So, onClick() becomes like this:
public void onClick(View v) {
// Update twitter status
try {
getTwitter().setStatus(editText.getText().toString());
} catch (TwitterException e) {
Log.d(TAG, "Twitter setStatus failed: " + e);
}
}Note that although we moved the code where we initialize our connection to the cloud, we still need the AsyncTask to deal with the fact that this call is still blocking and may take a while to complete, as it’s subject to network availability and latency.
Now, as we mentioned before when updating onCreate() and registering for preference updates, we need to handle what happens when user changes username or password. By registering prefs.registerOnSharedPreferenceChangeListener(this) in onCreate() and implementing OnSharedPreferenceChangeListener, we got a callback method onSharedPreferenceChanged() that system will invoke whenever preferences change. In this method, we simply invalidate the twitter object so next time it is needed, getTwitter() will recreate it.
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// invalidate twitter object
twitter = null;
}File System, Explained
So, where are these preferences stored on the device? How secure is my username and password? To answer that, we need to look at how Android filesystem is organized.
Exploring File System
There are two ways for you to access the file system on an Android device. One way is using Eclipse, the other command line.
In Eclipse, we use File Explorer view to access the file system. To open up the File Explorer view, go to Window→Show View→Other…→Android→File Explorer. You can also access File Explorer view via DDMS Perspective. Select DDMS Perspective in the top-right corner of your Eclipse
or go to Window→Open Perspective→Other…→DDMS. If you have multiple devices connected to your workstation, make sure you select which one you are working with in the Devices view. You should now be able to navigate through the file system of the device.
If you prefer the command line, you can always use adb shell to get to the shell of the device. From there you can explore the file system like you would on any other Unix platform.
File System Partitions
There are three main parts of the file system on every Android device. They are:
-
System partition at
/system/ -
SDCard at
/sdcard/ -
User Data partition at
/data/
System Partition
System partition is where your entire Android operating system is. This is the main partition containing all your preinstalled applications, system libraries, Android framework, linux command line tools, and so on.
System partition is mounted read-only, meaning you as developer have very little influence over it. As such, this partition is of limited interest to us.
The system partition in the Emulator corresponds to system.img file in your platform images directory, located in android-sdk/platforms/android-8/images/ folder.
SDCard Partition
SDCard partition is a free-for-all mass storage. Your app can read files from this partition as well as write files to it if it holds WRITE_TO_EXTERNAL_STORAGE permission. This is a great place to store large files, such as music, photos, videos and similar.
Note that since FroYo version of Android, /sdcard mount point appears in Eclipse File Explorer under /mnt/sdcard location. This is because of the new feature in FroYo to allow for storing and running applications on the SDCard as well.
As an app developer, SDCard partition is very useful and important to you. At the same time, this partition is not very structured.
This partition typically corresponds to sdcard.img in your Android Virtual Device (AVD) directory. This directory is in you ~/.android/avd/ folder and will have a subdirectory for each specific virtual device. On the physical device, it is an actual SD card.
User Data Partition
As user and app developer, the most important partition is the User Data partition. This is where all your user data is stored, all the downloaded apps are located, and most importantly, all apps' data is. This includes both preinstalled apps as well as user-downloaded apps.
So, while user apps are stored in /data/app/ folder, the most important folder to us as app developer is /data/data/ folder. More specifically, within this folder there’s a subfolder corresponding to each app. This folder is identified by the Java package that this app used to sign itself. Again, this is why Java packages are important to Android security.
Android framework provides number of handy methods as part of context that help you access user data file system from within your application. Take a look at getFilesDir() for example.
This partition typically corresponds to user-data.img in your Android Virtual Device (AVD) directory. As before, this directory is in your ~/.android/avd/ folder and will have a subdirectory for each specific virtual device.
When you create a new app, you assign your Java code to a specific package. Typically, this package follows the Java convention of reverse domain name plus app name. For example, Yamba app is in com.marakana.yamba package. So, once installed, Android creates a special folder just for this app under /data/data/com.marakana.yamba/. This folder is the cornerstone of our private secured file system dedicated to each app.
There will be sub-folders in /data/data/com.marakana.yamba2/, but they are well-defined. For example, the preferences are in /data/data/com.marakana.yamba2/shared_prefs/. As a matter of fact, if you open up DDMS perspective in your Eclipse and select File Explorer, you can navigate to this partition. You will probably see com.marakana.yamba2_preferences.xml file in there. You could pull this file and exam in it, or you could use adb shell.
adb shell is another one of those common adb subcommands to access the shell of your device (either physical or virtual). For instance, you could just open up your command line terminal and type:
[user:~]> adb shell # cd /data/data/com.marakana.yamba2/shared_prefs # cat com.marakana.yamba2_preferences.xml <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <string name="password">password</string> <string name="apiRoot">http://yamba.marakana.com/api</string> <string name="username">student</string> </map> #
This XML file represents the storage for all our preference data for this application. As you can see, our username, password and API root are all stored in there.
File System Security
So, how secure is this? This is a common question by security folks. Username and password stored in clear text always raises eyebrows.
To answer this question, I usually compare it to finding someone’s laptop on the street. While indeed we can easily gain access to the "hard-drive" via adb tool, that doesn’t mean we have a way of reading its data. Each folder under /data/data/ is going to belong to a separate user account. This user account is managed by Linux. Unless our app is that app, it won’t have access to that folder. So, short of us reading byte-by-byte on the physical device, even clear-text data is secure.
On the Emulator, we have root permissions meaning we can explore entire file system. This is useful for development purposes.
Summary
At this point, the user can specify the username and password for the micro-blogging site. This makes the app usable to way more people than the the version that had this information previously hard-coded.
Figure 7.6, “Yamba Completion” illustrates what we have done so far as part of the design outlined in Figure 5.4, “Yamba Design Diagram”:












Add a comment



Add a comment