Skip to content

Commit 11f03b4

Browse files
committed
DO NOT MERGE
cherrypick from jb-dev Change-Id: I9579a7eb04cb39d3b457dde390b6b2a7b240a429 docs: android training - Data Storage Change-Id: I62b6d5385355555aa3e184bfd4418a0d2e0c1719
1 parent 32a1d2d commit 11f03b4

File tree

6 files changed

+906
-1
lines changed

6 files changed

+906
-1
lines changed

docs/html/guide/topics/data/data-storage.jd

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,12 @@ save files. This can be a removable storage media (such as an SD card) or an int
232232
(non-removable) storage. Files saved to the external storage are world-readable and can
233233
be modified by the user when they enable USB mass storage to transfer files on a computer.</p>
234234

235-
<p class="caution"><strong>Caution:</strong> External files can disappear if the user mounts the
235+
<p>It's possible that a device using a partition of the
236+
internal storage for the external storage may also offer an SD card slot. In this case,
237+
the SD card is <em>not</em> part of the external storage and your app cannot access it (the extra
238+
storage is intended only for user-provided media that the system scans).</p>
239+
240+
<p class="caution"><strong>Caution:</strong> External storage can become unavailable if the user mounts the
236241
external storage on a computer or removes the media, and there's no security enforced upon files you
237242
save to the external storage. All applications can read and write files placed on the external
238243
storage and the user can remove them.</p>
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
page.title=Saving Data in SQL Databases
2+
parent.title=Data Storage
3+
parent.link=index.html
4+
5+
trainingnavtop=true
6+
previous.title=Saving Data in Files
7+
previous.link=files.html
8+
9+
@jd:body
10+
11+
12+
<div id="tb-wrapper">
13+
<div id="tb">
14+
15+
<h2>This lesson teaches you to</h2>
16+
<ol>
17+
<li><a href="#DefineContract">Define a Schema and Contract</a></li>
18+
<li><a href="#DbHelper">Create a Database Using a SQL Helper</a></li>
19+
<li><a href="#WriteDbRow">Put Information into a Database</a></li>
20+
<li><a href="#ReadDbRow">Read Information from a Database</a></li>
21+
<li><a href="#DeleteDbRow">Delete Information from a Database</a></li>
22+
<li><a href="#UpdateDbRow">Update a Database</a></li>
23+
</ol>
24+
25+
<h2>You should also read</h2>
26+
<ul>
27+
<li><a href="{@docRoot}guide/topics/data/data-storage.html#db">Using Databases</a></li>
28+
</ul>
29+
30+
<!--
31+
<h2>Try it out</h2>
32+
33+
<div class="download-box">
34+
<a href="{@docRoot}shareables/training/Sample.zip" class="button">Download the sample</a>
35+
<p class="filename">Sample.zip</p>
36+
</div>
37+
-->
38+
39+
</div>
40+
</div>
41+
42+
43+
<p>Saving data to a database is ideal for repeating or structured data,
44+
such as contact information. This class assumes that you are
45+
familiar with SQL databases in general and helps you get started with
46+
SQLite databases on Android. The APIs you'll need to use a database
47+
on Android are available in the {@link android.database.sqlite} package.</p>
48+
49+
50+
<h2 id="DefineContract">Define a Schema and Contract</h2>
51+
52+
<p>One of the main principles of SQL databases is the schema: a formal
53+
declaration of how the database is organized. The schema is reflected in the SQL
54+
statements that you use to create your database. You may find it helpful to
55+
create a companion class, known as a <em>contract</em> class, which explicitly specifies
56+
the layout of your schema in a systematic and self-documenting way.</p>
57+
58+
<p>A contract class is a container for constants that define names for URIs,
59+
tables, and columns. The contract class allows you to use the same constants
60+
across all the other classes in the same package. This lets you change a column
61+
name in one place and have it propagate throughout your code.</p>
62+
63+
<p>A good way to organize a contract class is to put definitions that are
64+
global to your whole database in the root level of the class. Then create an inner
65+
class for each table that enumerates its columns.</p>
66+
67+
<p class="note"><strong>Note:</strong> By implementing the {@link
68+
android.provider.BaseColumns} interface, your inner class can inherit a primary
69+
key field called {@code _ID} that some Android classes such as cursor adaptors
70+
will expect it to have. It's not required, but this can help your database
71+
work harmoniously with the Android framework.</p>
72+
73+
<p>For example, this snippet defines the table name and column names for a
74+
single table:</p>
75+
76+
77+
<pre>
78+
public static abstract class FeedEntry implements BaseColumns {
79+
public static final String TABLE_NAME = &quot;entry&quot;;
80+
public static final String COLUMN_NAME_ENTRY_ID = &quot;entryid&quot;;
81+
public static final String COLUMN_NAME_TITLE = &quot;title&quot;;
82+
public static final String COLUMN_NAME_SUBTITLE = &quot;subtitle&quot;;
83+
...
84+
}
85+
</pre>
86+
87+
88+
<p>To prevent someone from accidentally instantiating the contract class, give
89+
it an empty constructor. </p>
90+
91+
<pre>
92+
// Prevents the FeedReaderContract class from being instantiated.
93+
private FeedReaderContract() {}
94+
</pre>
95+
96+
97+
98+
<h2 id="DbHelper">Create a Database Using a SQL Helper</h2>
99+
100+
<p>Once you have defined how your database looks, you should implement methods
101+
that create and maintain the database and tables. Here are some typical
102+
statements that create and delete a table:</P>
103+
104+
<pre>
105+
private static final String TEXT_TYPE = &quot; TEXT&quot;;
106+
private static final String COMMA_SEP = &quot;,&quot;;
107+
private static final String SQL_CREATE_ENTRIES =
108+
&quot;CREATE TABLE &quot; + FeedReaderContract.FeedEntry.TABLE_NAME + &quot; (&quot; +
109+
FeedReaderContract.FeedEntry._ID + &quot; INTEGER PRIMARY KEY,&quot; +
110+
FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +
111+
FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
112+
... // Any other options for the CREATE command
113+
&quot; )&quot;;
114+
115+
private static final String SQL_DELETE_ENTRIES =
116+
&quot;DROP TABLE IF EXISTS &quot; + TABLE_NAME_ENTRIES;
117+
</pre>
118+
119+
<p>Just like files that you save on the device's <a
120+
href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal
121+
storage</a>, Android stores your database in private disk space that's associated
122+
application. Your data is secure, because by default this area is not
123+
accessible to other applications.</p>
124+
125+
<p>A useful set of APIs is available in the {@link
126+
android.database.sqlite.SQLiteOpenHelper} class.
127+
When you use this class to obtain references to your database, the system
128+
performs the potentially
129+
long-running operations of creating and updating the database only when
130+
needed and <em>not during app startup</em>. All you need to do is call
131+
{@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase} or
132+
{@link android.database.sqlite.SQLiteOpenHelper#getReadableDatabase}.</p>
133+
134+
<p class="note"><strong>Note:</strong> Because they can be long-running,
135+
be sure that you call {@link
136+
android.database.sqlite.SQLiteOpenHelper#getWritableDatabase} or {@link
137+
android.database.sqlite.SQLiteOpenHelper#getReadableDatabase} in a background thread,
138+
such as with {@link android.os.AsyncTask} or {@link android.app.IntentService}.</p>
139+
140+
<p>To use {@link android.database.sqlite.SQLiteOpenHelper}, create a subclass that
141+
overrides the {@link
142+
android.database.sqlite.SQLiteOpenHelper#onCreate onCreate()}, {@link
143+
android.database.sqlite.SQLiteOpenHelper#onUpgrade onUpgrade()} and {@link
144+
android.database.sqlite.SQLiteOpenHelper#onOpen onOpen()} callback methods. You may also
145+
want to implement {@link android.database.sqlite.SQLiteOpenHelper#onDowngrade onDowngrade()},
146+
but it's not required.</p>
147+
148+
<p>For example, here's an implementation of {@link
149+
android.database.sqlite.SQLiteOpenHelper} that uses some of the commands shown above:</p>
150+
151+
<pre>
152+
public class FeedReaderDbHelper extends SQLiteOpenHelper {
153+
// If you change the database schema, you must increment the database version.
154+
public static final int DATABASE_VERSION = 1;
155+
public static final String DATABASE_NAME = &quot;FeedReader.db&quot;;
156+
157+
public FeedReaderDbHelper(Context context) {
158+
super(context, DATABASE_NAME, null, DATABASE_VERSION);
159+
}
160+
public void onCreate(SQLiteDatabase db) {
161+
db.execSQL(SQL_CREATE_ENTRIES);
162+
}
163+
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
164+
// This database is only a cache for online data, so its upgrade policy is
165+
// to simply to discard the data and start over
166+
db.execSQL(SQL_DELETE_ENTRIES);
167+
onCreate(db);
168+
}
169+
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
170+
onUpgrade(db, oldVersion, newVersion);
171+
}
172+
}
173+
</pre>
174+
175+
<p>To access your database, instantiate your subclass of {@link
176+
android.database.sqlite.SQLiteOpenHelper}:</p>
177+
178+
<pre>
179+
FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());
180+
</pre>
181+
182+
183+
184+
185+
<h2 id="WriteDbRow">Put Information into a Database</h2>
186+
187+
<p>Insert data into the database by passing a {@link android.content.ContentValues}
188+
object to the {@link android.database.sqlite.SQLiteDatabase#insert insert()} method:</p>
189+
190+
<pre>
191+
// Gets the data repository in write mode
192+
SQLiteDatabase db = mDbHelper.getWritableDatabase();
193+
194+
// Create a new map of values, where column names are the keys
195+
ContentValues values = new ContentValues();
196+
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID, id);
197+
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title);
198+
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_CONTENT, content);
199+
200+
// Insert the new row, returning the primary key value of the new row
201+
long newRowId;
202+
newRowId = db.insert(
203+
FeedReaderContract.FeedEntry.TABLE_NAME,
204+
FeedReaderContract.FeedEntry.COLUMN_NAME_NULLABLE,
205+
values);
206+
</pre>
207+
208+
<p>The first argument for {@link android.database.sqlite.SQLiteDatabase#insert insert()}
209+
is simply the table name. The second argument provides
210+
the name of a column in which the framework can insert NULL in the event that the
211+
{@link android.content.ContentValues} is empty (if you instead set this to {@code "null"},
212+
then the framework will not insert a row when there are no values).</p>
213+
214+
215+
216+
217+
<h2 id="ReadDbRow">Read Information from a Database</h2>
218+
219+
<p>To read from a database, use the {@link android.database.sqlite.SQLiteDatabase#query query()}
220+
method, passing it your selection criteria and desired columns.
221+
The method combines elements of {@link android.database.sqlite.SQLiteDatabase#insert insert()}
222+
and {@link android.database.sqlite.SQLiteDatabase#update update()}, except the column list
223+
defines the data you want to fetch, rather than the data to insert. The results of the query
224+
are returned to you in a {@link android.database.Cursor} object.</p>
225+
226+
<pre>
227+
SQLiteDatabase db = mDbHelper.getReadableDatabase();
228+
229+
// Define a <em>projection</em> that specifies which columns from the database
230+
// you will actually use after this query.
231+
String[] projection = {
232+
FeedReaderContract.FeedEntry._ID,
233+
FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE,
234+
FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED,
235+
...
236+
};
237+
238+
// How you want the results sorted in the resulting Cursor
239+
String sortOrder =
240+
FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED + " DESC";
241+
242+
Cursor c = db.query(
243+
FeedReaderContract.FeedEntry.TABLE_NAME, // The table to query
244+
projection, // The columns to return
245+
selection, // The columns for the WHERE clause
246+
selectionArgs, // The values for the WHERE clause
247+
null, // don't group the rows
248+
null, // don't filter by row groups
249+
sortOrder // The sort order
250+
);
251+
</pre>
252+
253+
<p>To look at a row in the cursor, use one of the {@link android.database.Cursor} move
254+
methods, which you must always call before you begin reading values. Generally, you should start
255+
by calling {@link android.database.Cursor#moveToFirst}, which places the "read position" on the
256+
first entry in the results. For each row, you can read a column's value by calling one of the
257+
{@link android.database.Cursor} get methods, such as {@link android.database.Cursor#getString
258+
getString()} or {@link android.database.Cursor#getLong getLong()}. For each of the get methods,
259+
you must pass the index position of the column you desire, which you can get by calling
260+
{@link android.database.Cursor#getColumnIndex getColumnIndex()} or
261+
{@link android.database.Cursor#getColumnIndexOrThrow getColumnIndexOrThrow()}.
262+
For example:</p>
263+
264+
<pre>
265+
cursor.moveToFirst();
266+
long itemId = cursor.getLong(
267+
cursor.getColumnIndexOrThrow(FeedReaderContract.FeedEntry._ID)
268+
);
269+
</pre>
270+
271+
272+
273+
274+
<h2 id="DeleteDbRow">Delete Information from a Database</h2>
275+
276+
<p>To delete rows from a table, you need to provide selection criteria that
277+
identify the rows. The database API provides a mechanism for creating selection
278+
criteria that protects against SQL injection. The mechanism divides the
279+
selection specification into a selection clause and selection arguments. The
280+
clause defines the columns to look at, and also allows you to combine column
281+
tests. The arguments are values to test against that are bound into the clause.
282+
Because the result isn't handled the same as a regular SQL statement, it is
283+
immune to SQL injection.</p>
284+
285+
<pre>
286+
// Define 'where' part of query.
287+
String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + &quot; LIKE ?&quot;;
288+
// Specify arguments in placeholder order.
289+
String[] selelectionArgs = { String.valueOf(rowId) };
290+
// Issue SQL statement.
291+
db.delete(table_name, mySelection, selectionArgs);
292+
</pre>
293+
294+
295+
296+
<h2 id="UpdateDbRow">Update a Database</h2>
297+
298+
<p>When you need to modify a subset of your database values, use the {@link
299+
android.database.sqlite.SQLiteDatabase#update update()} method.</p>
300+
301+
<p>Updating the table combines the content values syntax of {@link
302+
android.database.sqlite.SQLiteDatabase#insert insert()} with the {@code where} syntax
303+
of {@link android.database.sqlite.SQLiteDatabase#delete delete()}.</p>
304+
305+
<pre>
306+
SQLiteDatabase db = mDbHelper.getReadableDatabase();
307+
308+
// New value for one column
309+
ContentValues values = new ContentValues();
310+
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title);
311+
312+
// Which row to update, based on the ID
313+
String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + &quot; LIKE ?&quot;;
314+
String[] selelectionArgs = { String.valueOf(rowId) };
315+
316+
int count = db.update(
317+
FeedReaderDbHelper.FeedEntry.TABLE_NAME,
318+
values,
319+
selection,
320+
selectionArgs);
321+
</pre>
322+

0 commit comments

Comments
 (0)