In this series, we are learning about Android SDK development from scratch. We have already become acquainted with the structure and basic elements in an Android application, including the resources, Manifest, and user interface. As soon as you start to develop functional apps for Android, you will need to store data of one kind or another. The Android platform offers a range of options for data storage in your apps, which we will examine in this tutorial.
Introduction
Broadly speaking, there are five main options for data storage in Android apps: by saving data to the app Shared Preferences, to internal storage (which is private to your app), to external storage (which is public to the device), to databases, and to Web resources accessed via the device's Internet connection. We will not demonstrate each of these options fully, but will take an overview of the basics with each approach, giving you a sense of how to choose a storage option each time you need persistent data.
1. Shared Preferences
Step 1
Shared Preferences let you store primitive data types in key-value pairs. The app Shared Preferences file is typically the simplest data storage option, but it is inherently limited in terms of what you can store. You can save primitive type numbers (int, long, and float), boolean values, and text Strings. You assign a name to each value you store and retrieve it each subsequent time the app runs. Since you are most likely to use the Shared Preferences in the first apps you create, we will run through the process in a little more detail than the other options, which you can build your knowledge of as needed.
You can try this code out in your main Activity class and test it when we run the app later on in this series. Shared Preferences are ideally suited to user configuration options for your apps, like to choose appearance settings. Remember we created a simple button that when the user clicked it, the text displayed on it changed to "Ouch". Let's imagine that once the user clicked the button once, we want the button to maintain the "Ouch" text, even on subsequent runs of the app. This means that the initial text only appears until the first time the user clicks the button.
Let's add some Shared Preferences to the app. At the top of the class, before the onCreate method, let's choose a name for the Shared Preferences:
public static final String MY_APP_PREFS = "MyAppPrefs";
Using the "public static" modifiers lets us access this variable in any class within the app, so we only need to store the preferences name String here. We use uppercase because the variable is a constant, hence the "final" modifier. Each time you retrieve or set a data item in the app preferences, you must use the same name.
Step 2
Let's write the Shared Preferences now. In your onClick method, after the line in which you set the "Ouch" text on the button, retrieve the Shared Preferences using their name:
SharedPreferences thePrefs = getSharedPreferences(MY_APP_PREFS, 0);
You need to add an import for the "android.content.SharedPreferences" class. Hover over the "SharedPreferences" text and use the Eclipse prompts to do this. The first parameter is the preferences name we defined, the second is the mode, which we use for the default option.
Now you need to retrieve an Editor for the Shared Preferences if you want to set values:
SharedPreferences.Editor prefsEd = thePrefs.edit();
Now we can write a value to the Shared Preferences:
prefsEd.putBoolean("btnPressed", true);
We use a Boolean type since the user has either pressed the button or not. The Editor provides specific methods for the different types you can store in the Shared Preferences, with each method taking name and value parameters. Finally, we need to commit the edits:
prefsEd.commit();
Step 3
Let's now use the stored value to determine what should display when the user runs the app. In onCreate, after the existing code, get the Shared Preferences:
SharedPreferences thePrefs = getSharedPreferences(MY_APP_PREFS, 0);
We don't need an editor this time as we are only retrieving a value:
boolean pressed = thePrefs.getBoolean("btnPressed", false);
We retrieve the value using the same name we set it with, reading the result into a variable. If the value has not yet been set, the second parameter is returned. This is the default value, which is false. Now let's use the value:
if(pressed) theButton.setText("Ouch");
If the user pressed the button when the app was run before, the button displays "Ouch" straight away. You can see this in action when we run the app later in the series. This is a trivial example to demonstrate the process of using Shared Preferences. You will find them most useful in your own apps for user preferences in terms of the app's appearance and behavior.
2. Private Internal Files
Step 1
You can save files to both the internal and external storage on the user's device. If you save a file to internal storage, Android treats this as private to your app. Such files are essentially part of your app as far as the device is concerned, there is no direct access to them outside of the app, and they are removed if the app itself is removed.
You can create a file in internal storage using an output stream as follows:
FileOutputStream fileOut = openFileOutput("my_file", Context.MODE_PRIVATE);
You need to add an import for the "java.io.FileOutputStream" class. We pass a file name and mode, choosing private mode so that the file can only be used by the app. If you try adding this code to your Activity now, for example in the onClick method, Eclipse will indicate an error. This is because when you carry out input/ output operations your app may encounter errors that you need to take measures to cope with. If your input/ output operation cannot complete it, an Exception is thrown and your app will stop functioning. To keep the app running, if this happens, we enclose our input/ output code in a try block:
try{ FileOutputStream fileOut = openFileOutput("my_file", Context.MODE_PRIVATE); } catch(IOException ioe){ Log.e("APP_TAG", "IO Exception", ioe); }
If the input/ output operation causes an Exception, the code inside the catch block executes, which simply writes the error details to the Log. You will use the Log class often in your apps (import "android.util.Log"), as it lets you see what is happening as the code executes. You can define a class variable for the String tag we use as first parameter here. Then you can see details of the Exception in the Android LogCat if something goes wrong.
Step 2
Back in the try block, after creating the file output stream, you can attempt to write to the file:
String fileContent = "my data file content"; fileOut.write(fileContent.getBytes());
Once you have written what you need to the file, you should close it:
fileOut.close();
Step 3
When you need to retrieve the content of internal files, you can use the following process:
try{ FileInputStream fileIn = openFileInput("my_file"); //read the file } catch(IOException ioe){ Log.e("APP_TAG", "IO Exception", ioe); }
Inside the try block, read the file contents using a Buffered Reader:
InputStreamReader streamIn = new InputStreamReader(fileIn); BufferedReader fileRead = new BufferedReader(streamIn); StringBuilder fileBuild = new StringBuilder(""); String fileLine=fileRead.readLine(); while(fileLine!=null){ fileBuild.append(fileLine+"\n"); fileLine=fileRead.readLine(); } String fileText = fileBuild.toString(); streamIn.close();
Don't be intimidated by the number of different objects involved, this is standard for Java input/ output. The while loop executes for each line in the file. After execution, the "fileText" variable stores the file content as a String you can then make use of.
3. Public External Files
Step 1
Your apps can save files to external storage if the user device has some available. External storage can be an SD card or other removable media, or it can be internal storage that the user cannot remove but that the system regards as external. When you save a file to external storage, it is public and there is no way for you to stop the user or another application from accessing it.
Before you attempt to save data to external storage, you must first check that there is some available, as this will not always be the case:
String extStorageState = Environment.getExternalStorageState();
The system returns the information as a String, which you can then analyze, checking it against the Environment class fields for various external storage states:
if(Environment.MEDIA_MOUNTED.equals(extStorageState)){ //ok to go ahead and read/ write to external storage } else if(Environment.MEDIA_MOUNTED_READ_ONLY.equals(extStorageState)){ //can only read } else{ //cannot read or write }
Even if there is external storage on the device, we cannot assume that the app can write to it.
Step 2
After verifying that you can write to external storage, you then need to retrieve the directory location to save your files in. The following applies to apps targeting API levels 8 and onwards:
File myFile = new File(getExternalFilesDir(null), "MyFile.txt");
You can then write to and read from the file. You will also need the following permission in your Manifest file:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
When your apps become more advanced, you may wish to save to files that are shared by other applications. In such cases, you can use the various public directories for common items, such as pictures and music files.
4. Databases
As soon as your apps require more complex structured data than you can reasonably store in Shared Preferences or internal/ external files, you should consider using databases. Android provides support for creating and accessing SQLite databases within your apps. When you create a database, it is private to your app.
The recommended way to utilize SQLite databases in your Android apps is to use a class which extends SQLiteOpenHelper. Inside this class, define the properties of the database, creating class variables in which you define the database table names and their SQL creation strings, as in the following example:
private static final String NOTE_TABLE_CREATE = "CREATE TABLE Note (noteID INTEGER PRIMARY KEY AUTOINCREMENT, " + "noteTxt TEXT);";
This example represents a very simple table with two columns, one for ID and the other for text, with each record storing a user note. Inside your SQLiteOpenHelper class, you can override the onCreate method to create your database. Elsewhere in the app, like in an Activity class, you can then access the database via the SQLiteOpenHelper, using the getWritableDatabase method to insert new records and the getReadableDatabase method to query for existing records, you can then display within your app UI.
When iterating through query results, your apps will use the Cursor class, which reference each row in your result set in turn.
5. Internet Data
Many apps use Internet data resources, with the app sometimes essentially comprising an interface to a Web data source. You can use the Internet connection on the user device to store and retrieve data from the Web whenever the user has a functioning Internet connection. To do so, you need to add the "android.permission.INTERNET" permission to your Manifest.
If you want to fetch Internet data in your apps, you need to make sure you carry out this processing off the app's main UI thread. By using an AsyncTask, you can fetch data from the Web in a background process, write the results to the UI when they arrive, and let the UI continue to function while they are fetched.
You can add an inner AsyncTask class inside an Activity class, then create an instance of the AsyncTask in the Activity whenever you want the data fetched. By including the doInBackground and onPostExecute methods in your AsyncTask, you can retrieve the results of fetching the data in the Activity, letting you write them to the user interface.
Fetching Web data is an intermediate task better left until you have grasped the basics of Android development using data on the device. However, you may find it useful for many apps as it lets you exploit the user device connectivity. Java and Android provide utilities for handling returned structured data, such as JSON feeds.
Conclusion
This tutorial gave you an overview of your data storage choices when developing Android apps. Whatever option you choose is determined by the details of the app you are working on, as the methods of each option are suited to specific needs. In the next part of this series, we will look at connecting physical devices to your Eclipse installation as well as creating virtual devices. After that, we will explore running apps on either type of device. Towards the end of the series, we will examine common classes and the Android Activity lifecycle, preparing you to start working on your own apps.
Comments