Thursday, October 24, 2013

Android Persistence with preferences and files - Tutorial

File based persistence in Android
This tutorial describes how to save key-value pairs using the preference API in Android. It also explains how to read and write files in Android. It is based on Eclipse 4.3, Java 1.6 and Android 4.3.

1. File based persistence

1.1. Methods of local data persistence

Android allows to persists application data via the file system. For each application the Android system creates a data/data/[application package] directory.
Android supports the following ways of storing data in the local file system:
  • Files - You can create and update files
  • Preferences - Android allows you to save and retrieve persistent key-value pairs of primitive data type.
  • SQLite database - instances of SQLite databases are also stored on the local file system.
Files are saved in the files folder and application settings are saved as XML files in the shared_prefsfolder.
If your application creates an SQLite database this database is saved in the main application directory under the databases folder.
The following screenshot shows a file system which contains file, cache files and preferences.
Screenshot of the file system with a few files
Only the application can write into its application directory. It can create additional sub-directories in this application directory. For these sub-directories, the application can grant read or write permissions for other applications.

1.2. Internal vs. external storage

Android has internal storage and external storage. External storage is not private and may not always be available. If for example the Android device is connected with a computer, the computer may mount the external system via USB and that makes this external storage not avaiable for Android applications.

1.3. Application on external storage

As of Android 8 SDK level it is possible to define that the application can or should be placed on external storage. For this set the android:installLocation to preferExternal or auto.
In this case certain application components may be stored on an encrypted external mount point. Database and other private data will still be stored in the internal storage system.

2. Preferences

2.1. Storing key-value pairs

Android supports the usage of the SharedPreferences class for persisting key-value pairs (preferences)of primitive data types in the Android file system.
The definition of these preferences can be done via an XML resource.
The PreferenceManager class provides methods to get access to preferences stored in a certain file. The following code shows how to access preferences from a certain file
# getting preferences from a specified file
SharedPreferences settings = getSharedPreferences("Test", Context.MODE_PRIVATE);

Tip

Preferences are typically created private and can be accessed via all application components. Sharing data with other application with world readable or writable preference file is rarely used, as the external component would need to know the exact filename and location of the file.
The default preferences are available from any component via thePreferenceManager.getDefaultSharedPreferences(this) method call.
Preference value are accessed via the key and the instance of the SharedPreferences class, as demonstrated in the following listing.
String username = preferences.getString("username", "n/a"); 
To create or change preferences you have to call the edit() method on the SharedPreferencesobject. Once you have changed the value you have to call the apply() method to apply your asynchronously to the file system. The usage of the commit() method is discouraged, as it write the changes synchronously to the file system.
Editor edit = preferences.edit();
edit.putString("username", "new_value_for_user");
edit.apply();

2.2. Preference Listener

You can listen to changes in the preferences via theregisterOnSharedPreferenceChangeListener() method on SharedPreferences.
SharedPreferences prefs = 
PreferenceManager.getDefaultSharedPreferences(this);

// Instance field for listener
listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// Your Implementation
}
};

prefs.registerOnSharedPreferenceChangeListener(listener);
One watch out is that SharedPreferences keeps listeners in a WeakHashMap hence listener may be recycled if your code does not hold a reference to it.

3. Tutorial: Prerequirements

The following tutorial is based on the "de.vogella.android.socialapp" example from Android action bar tutorial .

4. Exercise: Using preferences and the preference activity

4.1. Using preferences

This exercise assumes that you have created an Android project called de.vogella.android.social with one entry called Settings in the action bar.
Create an Android XML resource called preferences.xml of the PreferenceScreen type.
How to create an XML file for storing preference value definitions
Open the file via right-click and Open with → Android XML Resource Editor.
Press the Add button and add a PreferenceCategory and add two preferences of typeEditTextPreferences. They should be called User and Password.
Adding a category to the preference XML file
Adding the field "user" to the preference XML file
Adding the field "password" to the preference XML file
You can also enter values for other properties of EditTextField, e.g. the inputMethod.
Add the following attribute to the XML definition of your password field to make the input quoted with*.
android:inputType="textPassword" 
Create the class MyPreferencesActivity which extends PreferenceActivity. This activity loads the preference.xml file and allows the user to change the values.
package de.vogella.android.socialapp;

import android.os.Bundle;
import android.preference.PreferenceActivity;

public class MyPreferencesActivity extends PreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
Register this class as an activity in your AndroidManifest.xml file.
To make use of our new preference activity and the preference values we adjust your main activity. Ensure that the layout of the activity contains two buttons.
The first button will show the current values of the preferences via a Toast and the second button will revert the maintained user name to demonstrate how you could change the preferences via code.
package de.vogella.android.socialapp;

import android.app.Activity;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

public class OverviewActivity extends Activity {
SharedPreferences preferences;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button = (Button) findViewById(R.id.Button01);
// Initialize preferences
preferences = PreferenceManager.getDefaultSharedPreferences(this);

button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
String username = preferences.getString("username", "n/a");
String password = preferences.getString("password", "n/a");
showPrefs(username, password);
}
});

Button buttonChangePreferences = (Button) findViewById(R.id.Button02);
buttonChangePreferences.setOnClickListener(new OnClickListener() {
public void onClick(View v) {

updatePreferenceValue();
}
});
}

private void showPrefs(String username, String password){
Toast.makeText(OverviewActivity.this,
"Input: " + username + " and password: "
+ password, Toast.LENGTH_LONG).show();

}

private void updatePreferenceValue(){
Editor edit = preferences.edit();
String username = preferences.getString("username", "n/a");
// We will just revert the current user name and save again
StringBuffer buffer = new StringBuffer();
for (int i = username.length() - 1; i >= 0; i--) {
buffer.append(username.charAt(i));
}
edit.putString("username", buffer.toString());
edit.commit();
// A toast is a view containing a quick little message for the
// user. We give a little feedback
Toast.makeText(OverviewActivity.this,
"Reverted string sequence of user name.",
Toast.LENGTH_LONG).show();
}
Open the preference activity via the onOptionsItemSelected() method as demonstrated in the following listing.
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.mainmenu, menu);
return true;
}

// This method is called once the menu is selected
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// We have only one menu option
case R.id.preferences:
// Launch Preference activity
Intent i = new Intent(OverviewActivity.this, MyPreferencesActivity.class);
startActivity(i);
// Some feedback to the user
Toast.makeText(OverviewActivity.this, "Enter your user credentials.",
Toast.LENGTH_LONG).show();
break;

}
return true;
}

4.2. Run

Run your application. Select from your ActionBar the Preference menu entry. You should be able to enter your user settings then press the back hardware button to return to your main activity. The saved values should be displayed in a small message windows (Toast) if you press your first button. If you press the second button the username should be reversed.
The running application showing the maintenance dialog for the field "user" in the preference activity

5. Android File API

5.1. Overview

Access to the file system is performed via the standard java.io classes.
Android provides also helper classes for creating and accessing new files and directories. For example the getDir(String, int) method would create or access a directory. TheopenFileInput(String s) method would open a file for input and openFileOutput(String s, int) would create a file.
int specifies the permissions which are:
  • MODE_PRIVATE - No access for other applications
  • MODE_WORLD_READABLE - Read access for other applications
  • MODE_WORLD_WRITABLE - Write access for other applications
  • MODE_WORLD_READABLE | MODE_WORLD_WRITABLE - Read / Write access
The following example shows the API usage.
private void writeFileToInternalStorage() {
String eol = System.getProperty("line.separator");
BufferedWriter writer = null;
try {
writer =
new BufferedWriter(new OutputStreamWriter(openFileOutput("myfile",
MODE_WORLD_WRITEABLE)));
writer.write("This is a test1." + eol);
writer.write("This is a test2." + eol);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void readFileFromInternalStorage() {
String eol = System.getProperty("line.separator");
BufferedReader input = null;
try {
input = new BufferedReader(new InputStreamReader(openFileInput("myfile")));
String line;
StringBuffer buffer = new StringBuffer();
while ((line = input.readLine()) != null) {
buffer.append(line + eol);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

5.2. How to access a shared file

Another application can access a file, which has been created with the MODE_WORLD_READABLEmodel. For this, it need to knows the package and file name. The following example shows this.
FileInputStream openFileInput = 
createPackageContext("the_package", 0).
openFileInput("thefile");

5.3. External storage

Android supports also access to an external storage system e.g. the SD card. All files and directories on the external storage system are readable for all applications with the correct permission.
To read from external storage your application need to have theandroid.permission.READ_EXTERNAL_STORAGE permission.
To write to the external storage system your application needs theandroid.permission.WRITE_EXTERNAL_STORAGE permission. You get the path to the external storage system via the Environment.getExternalStorageDirectory() method.
Via the following method call you can check the state of the external storage system. If the Android device is connected via USB to a computer, a SD card which might be used for the external storage system is not available.
Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) 
The following shows an example for reading from the external storage system.
private void readFileFromSDCard() {
File directory = Environment.getExternalStorageDirectory();
// assumes that a file article.rss is available on the SD card
File file = new File(directory + "/article.rss");
if (!file.exists()) {
throw new RuntimeException("File not found");
}
Log.e("Testing", "Starting to read");
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

No comments:

Post a Comment