Android Data
Juan E. Vargas
https://goo.gl/OfMONf
Data Storage Options on Android
Reading/Writing data into files is important for obvious reasons.
There are three alternatives for data storage on Android:
Data Storage Options on Android
Relatively small data can be stored as a collection of key-values via the SharedPreferences APIs.
Android file system is based on Linux ext4 and thus it offers functions found in many disk-based file systems.
SQLite is part of the Android Platform. It provides a relational DB for local storage in the device, i.e., it contains tables (rows and columns), indexes, transactions, joins, etc, that together with other features make up a DB schema.
SharedPreferences
Relatively small data can be stored as a collection of key-values (hash tables) via the SharedPreferences APIs.
Hash tables have nice properties: on average space is O(n), search is O(1), insert is O(1) and delete is O(1). Worst case is O(n).
A SharedPreferences object points to a file containing key-value pairs and provides methods to read and write them.
SharedPreferences files are managed by the framework and can be private or shared.
Getting a SharedPreferences Object
getSharedPreferences() can be used to access multiple shared preference files identified by name. This could be used to have a collection of activity preferences by a user.
getPreferences() can be used to open one shared preference file for each activity. This retrieves a default shared preference file that belongs to the activity, therefore you don't need to supply a name.
Just remember that you would be using hash tables
http://developer.android.com/training/basics/data-storage/shared-preferences.html
Working with Files
Android file system is based on Linux ext4 and thus it offers most of the disk-based functions found in most file systems.
File objects are created for reading or writing data in a sequential, start-to-finish order, without skipping around, e.g., image or sound files exchanged over a network.
There are several details worth knowing about where files can be stored to control accessibility. Once you know those details working with files for I/O is done in ways similar to the standard I/O packages in Java.
Working with Files
ACCESS/STORAGE | EXTERNAL | INTERNAL |
PRIVATE | | |
PUBLIC | | |
Awareness of access and type of storage is important
Internal Storage
Android devices have two file storage areas: "internal" and "external".
Internal storage: It's always available. By default, only the running app has access to internal storage files.
PROS: It is there.
CONS: It is limited
When users uninstall the app, the system removes all app's files from internal storage.
This is good for security because it ensures that neither the user nor other apps can access other files. This is bad because other apps should not expect those files to be there...
Getting an Internal File Directory
To get a file object representing an internal directory for your app:
getFilesDir()
To get a file object representing an internal directory for your app's temporary cache files:
getCacheDir()
To create a new file in one of those directories:
File file = new File(context.getFilesDir(), filename)
can you guess what the context is?
Context is an interface to global information about an application environment. Context is an abstract class provided by the Android system. It allows access to application-specific resources and classes, including calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.
Writing one line of text to internal file
public void writeFile(String sFileName, String sBody) {
try {
File root = new File(Environment.getExternalStorageDirectory(), "Notes");
if (!root.exists()) {
root.mkdirs();
}
File gpxfile = new File(root, sFileName);
FileWriter writer = new FileWriter(gpxfile);
writer.append(sBody);
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, "Error", Toast.LENGTH_SHORT).show();
}
}
Looks familiar?
can you guess what the environment is?
Reading text from an internal file
public void readFile( ) {
String locFileName = "enter a file name here";
ArrayList<String> lines = new ArrayList<String>();
try {
BufferedReader in = new BufferedReader(new FileReader(locFileName));
String line;
while ((line = in.readLine()) != null) {
lines.add(line);
}
in.close();
// do something with the content in lines;
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "Error in readFile", Toast.LENGTH_SHORT).show();
}
Toast.makeText(this, "Done", Toast.LENGTH_SHORT).show();
}
Looks familiar?
Pretty Simple, Right?
Runtime Permissions
Yes, the rules of the game have changed, again…
In the past, apps needed to obtain permissions at install time. From Android 6.0 (API level 23) and forward, apps must obtain permissions at runtime. This approach:
1. Streamlines the app install process; users don’t need to grant permissions at install/update time.
2. Gives users more control, e.g., depending on the circumstances (in a museum? in a park?) an app may need to access the camera but not the device location.
3. Users may revoke permissions at any time via the app's Settings screen.
Permission Groups
The URL below points to the list of Normal
and Dangerous Permissions:
https://developer.android.com/guide/topics/permissions/requesting.html#normal-dangerous
READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE belong to the Dangerous group.
Therefore the user must give explicit approval. More about that topic can be found at this URL:
External Storage (ES)
ES is the best place for files with no access restrictions, i.e., for files that you want to share with other apps, or files that users will access with an external computer.
ES is not always available; users can mount the ES as USB and in some cases remove from device.
ES is world-readable, therefore files saved as ES may be read outside of the app control.
When a user uninstalls an app, the system removes the app's files only if you save them in the app directory using getExternalFilesDir().
Obtaining Permission
Apps need to request access for read, write, or both.
This is done in the manifest file:
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
...
</manifest>
Save to External Storage
// is external storage available for read/write?
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
// is external storage available for read ?
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
Public or Private External Files
External files can be Public or Private.
This distinction is not necessary for internal files
External Public Files
public File getAlbumStorageDir(String albumName) {
// Get the directory for the user's public pictures directory.
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
Once you have the files open for I/O you can use the standard I/O packages in Java.
External Private Files
public File getAlbumStorageDir(Context context, String albumName) {
// Get the directory for the app's private pictures directory.
File file = new File(context.getExternalFilesDir(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
Note that “private” is the default
Once you have the files open for I/O you can use the standard I/O packages in Java.
Android SQLite
SQLite is part of the Android Platform. It provides a relational DB for local storage in the device, i.e., it contains tables (rows and columns), indexes, etc, that together with other features make up a DB schema.
SQLite was designed to operate as an embedded DBMS running under a very small footprint (~350KB) within a single cross-platform disk file.
SQLite implements most of the SQL92 standard and supports “ACID” transactions:
Atomic, Consistent, Isolated, Durable
As of this writing Android ships with SQLite version 3.7
SQL ACID
Atomicity: Requires that each transaction be "all or nothing": if one part of the transaction fails, the entire transaction fails, and the DB state is left unchanged.
Consistency: Any transaction will bring the DB from one valid state to another. Any data written to the DB must be valid according to all defined rules, including constraints, cascades, triggers, and any combination thereof. This does not guarantee correctness of the transaction.
Isolation: The concurrent execution of transactions results in a system state that would be obtained if transactions were executed serially.
The durability property ensures that once a transaction has been committed, the outcome will remain, even in the event of power loss, crashes, or errors.
Using SQLite3 from Console
Android ships with the Sqlite3 DB and can be used from the Android shell (command) window (adb). Here are the steps
3
2
1
juanbuntu
GSU Laptop
~/AndroidStudioProjects/Sdk/platform-tools/...
/home/juan/AndroidStudioProjects/sdk
Solus
cd /home/juan/Android/Sdk/platform-tools
to execute (I don’t want to mess up my $PATH variable)
/home/juan/Android/Sdk/platform-tools/adb shell
/home/juan/Android/Sdk/platform-tools/sqlite3
… etc …
look for Sdk/platform-tools
look for Sdk/platform-tools
4
5
Nice but the real power is to use sqlite in code.
6
7
CREATE Table cars ( Id INT, Name TEXT, Price Int);
INSERT INTO cars VALUES ( 1, 'Audi', 65000);
INSERT INTO cars VALUES ( 2, 'Mercedes', 55000);
INSERT INTO cars VALUES ( 3, 'Saab', 45000);
INSERT INTO cars VALUES ( 4, 'Porshe', 35000);
INSERT INTO cars VALUES ( 6, 'Jaguar', 25000);
INSERT INTO cars VALUES ( 7, 'Honda', 15000);
INSERT INTO cars VALUES ( 8, 'Toyota', 10000);
Android SQLite
Access to an SQLite DB is done via the Android File System.
Developers need to take into consideration the fact that operations must be asynchronous because access to files is typically slow.
For this reason access is often made via AsyncTask, AsyncQueryHandler, CursorLoader...
… Working with DBs in general is not difficult, just excruciatingly boring… because you have to take care of several details… if you need more information I suggests reading:
http://www.vogella.com/tutorials/AndroidSQLite/article.html
http://www.techotopia.com/index.php/An_Android_SQLite_Database_Tutorial
Defining a Schema and a Contract
Schemas are a formal declaration that specifies how a DB is organized.
Given the nature of mobile apps, the Android docs suggest having a companion class (contract) to explicitly specify schema layouts in a systematic and self-documenting way.
Thus a contract is a class acting as a template that defines names for URIs, tables, columns, indexes, etc..
The contract keeps a consistent set of constants across classes in the same package and this helps developers make changes in one place and propagate the changes throughout the code
Sample Contract Class
public final class FeedReaderContract {
// To prevent someone from accidentally instantiating the contract class,
// give it an empty constructor.
public FeedReaderContract() {}
/* Inner class that defines the table contents */
public static abstract class FeedEntry implements BaseColumns {
public static final String TABLE_NAME = "entry";
public static final String COLUMN_NAME_ENTRY_ID = "entryid";
public static final String COLUMN_NAME_TITLE = "title";
public static final String COLUMN_NAME_SUBTITLE = "subtitle";
...
}
}
Typical SQL Statements
private static final String TEXT_TYPE = " TEXT";
private static final String COMMA_SEP = ",";
private static final String SQL_CREATE_ENTRIES =
"CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +
FeedEntry._ID + " INTEGER PRIMARY KEY," +
FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +
FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
... // Any other options for the CREATE command
" )";
private static final String SQL_DELETE_ENTRIES =
"DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;
FeedEntry is the contract class. Fields in yellow background are part of the contract
If you need a quick review on SQL I recommend looking at this URL:
http://www.w3schools.com/sql/
Where are SQL DBs stored?
Android stores DBs in private disk space associated to the application. In this way the data is secure; by default this area is not accessible to other applications.
The system performs potentially long-running operations for creating and updating the DB only when needed and not during app startup.
The Android architecture defines a class called SQLiteOpenHelper which is used to create a DB and to provide version management.
SQLiteOpenHelper Methods
getDatabaseName( )
getReadableDatabase ( )
getWritableDatabase( )
EVENT Listeners:
onConfigure( )
onCreate( )
onDowngrade ( )
onUpgrade ( )
onOpen( )
Writing into a DB
// Get the data repository in write mode
SQLiteDatabase db = mDbHelper.getWritableDatabase();
// Create new map of values; column names are the keys
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_ENTRY_ID, id);
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_CONTENT, content);
// Insert new row, return primary key value of the new row
long newRowId;
newRowId = db.insert(
FeedEntry.TABLE_NAME,
FeedEntry.COLUMN_NAME_NULLABLE,
values);
Reading
From
DB
SQLiteDatabase db = mDbHelper.getReadableDatabase();
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
FeedEntry._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_UPDATED,
...
};
// How you want the results sorted in the resulting Cursor
String sortOrder =
FeedEntry.COLUMN_NAME_UPDATED + " DESC";
Cursor c = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // The columns to return
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
sortOrder // The sort order
);
android.database
This is a package that contains several SQLite DBMS classes and interfaces that apps need to use in order to manage DBs with content local to the device.
android.database.SQLiteDatabase
It provides the insert(), update(), & delete() methods
The next slides show some of the packages, classes and interfaces used to develop apps with DBs
android.database.sqlite
android.database.sqlite
android.database.sqlite is a package that contains classes to manage DBs in the device.
To create a db:
SQLiteDatabase aDbObj=openOrCreateDatabase("aDBName",MODE_PRIVATE,null);
Once the DB object is created, SQL statements that return NO DATA can be executed by
aDbObj.execSQL
Take a deep dive into the methods that can be used by objects of this class:
http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html
android.database.sqlite
aDbObj.execSQL
Once a DB is created, tables can be added:
aDbObj.execSQL("CREATE TABLE IF NOT EXISTS aTabName(Username VARCHAR,Password VARCHAR);");�
To insert records into a table DO NOT use:
mydatabase.execSQL("INSERT INTO aTabName VALUES('admin','admin');");
and from that point insertion, deletion, etc can be done to the DB and its tables.
The execSQL command does not return data.
Cursor
To get content from a DB and its tables we need a Cursor object.
Cursor is a class interface that provides random R/W access to the result set returned by a DB query. Cursor implementations are not required to be synchronized, therefore code using a Cursor from multiple threads should perform its own synchronization when using a Cursor,
for a detailed list of the methods visit:
http://developer.android.com/reference/android/database/Cursor.html
Cursor resultSet = aDbObj.rawQuery("Select * from aTabName",null);�resultSet.moveToFirst();�String username = resultSet.getString(1);�String password = resultSet.getString(2);
More Cursor Methods
getColumnCount()
Returns the total number of columns in table.
getColumnIndex(String columnName)
Returns the index number of a column by specifying the name of the column
getColumnName(int columnIndex)
Returns the name of the column by specifying the index of the column
getColumnNames()
Returns an array with the column names in the table.
getCount()
Returns the total number of rows in the cursor
getPosition()
Returns the current position of the cursor in the table
isClosed()
Returns true if the cursor is closed, false otherwise
SQLiteOpenHelper
Operations on DBs are transactional by nature. This is why there is a class called SQLiteOpenHelper that automatically manages the creation and updates of DB objects. By using this class you get transaction capabilities as a “free lunch”:
public class DBHelper extends SQLiteOpenHelper {� public DBHelper(){� super(context,DATABASE_NAME,null,1);� }� public void onCreate(SQLiteDatabase db) {}� public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {}�}
The next slides show a full developed app that:
Transferring to
AddressBook App
Even the “+” icon needs to be compatible with Android Specs. There is a site on the developers.android site with drawable objects.
another drawable object
Clicking on the names opens a view to view/edit the content
Inside the App
There are 3 classes
MainActivity
MainActivity
1
2
3
DisplayContact
Gets the text from the textView objects (name, phone, eMail, street, place) to the DB and back
DisplayContact
DisplayContact
using cursor to get stuff from DB
dbHelper
dbHelper