summaryrefslogtreecommitdiffstats
path: root/docs/html/resources/tutorials/notepad/notepad-ex3.jd
diff options
context:
space:
mode:
Diffstat (limited to 'docs/html/resources/tutorials/notepad/notepad-ex3.jd')
-rw-r--r--docs/html/resources/tutorials/notepad/notepad-ex3.jd358
1 files changed, 358 insertions, 0 deletions
diff --git a/docs/html/resources/tutorials/notepad/notepad-ex3.jd b/docs/html/resources/tutorials/notepad/notepad-ex3.jd
new file mode 100644
index 0000000..8737280
--- /dev/null
+++ b/docs/html/resources/tutorials/notepad/notepad-ex3.jd
@@ -0,0 +1,358 @@
+page.title=Notepad Exercise 3
+parent.title=Notepad Tutorial
+parent.link=index.html
+@jd:body
+
+
+<p><em>In this exercise, you will use life-cycle event callbacks to store and
+retrieve application state data. This exercise demonstrates:</em></p>
+<ul>
+<li><em>Life-cycle events and how your application can use them</em></li>
+<li><em>Techniques for maintaining application state</em></li>
+</ul>
+
+<div style="float:right;white-space:nowrap">
+ [<a href="notepad-ex1.html">Exercise 1</a>]
+ [<a href="notepad-ex2.html">Exercise 2</a>]
+ <span style="color:#BBB;">
+ [<a href="notepad-ex3.html" style="color:#BBB;">Exercise 3</a>]
+ </span>
+ [<a href="notepad-extra-credit.html">Extra Credit</a>]
+</div>
+
+<h2>Step 1</h2>
+
+<p>Import <code>Notepadv3</code> into Eclipse. If you see an error about
+<code>AndroidManifest.xml,</code> or some problems related to an Android zip
+file, right click on the project and select <strong>Android Tools</strong> &gt;
+<strong>Fix Project Properties</strong> from the popup menu. The starting point for this exercise is
+exactly where we left off at the end of the Notepadv2. </p>
+<p>The current application has some problems &mdash; hitting the back button when editing
+causes a crash, and anything else that happens during editing will cause the
+edits to be lost.</p>
+<p>To fix this, we will move most of the functionality for creating and editing
+the note into the NoteEdit class, and introduce a full life cycle for editing
+notes.</p>
+
+ <ol>
+ <li>Remove the code in <code>NoteEdit</code> that parses the title and body
+ from the extras Bundle.
+ <p>Instead, we are going to use the <code>DBHelper</code> class
+ to access the notes from the database directly. All we need passed into the
+ NoteEdit Activity is a <code>mRowId</code> (but only if we are editing, if creating we pass
+ nothing). Remove these lines:</p>
+ <pre>
+String title = extras.getString(NotesDbAdapter.KEY_TITLE);
+String body = extras.getString(NotesDbAdapter.KEY_BODY);</pre>
+ </li>
+ <li>We will also get rid of the properties that were being passed in
+ the <code>extras</code> Bundle, which we were using to set the title
+ and body text edit values in the UI. So delete:
+ <pre>
+if (title != null) {
+ mTitleText.setText(title);
+}
+if (body != null) {
+ mBodyText.setText(body);
+}</pre>
+ </li>
+ </ol>
+
+<h2>Step 2</h2>
+
+<p>Create a class field for a <code>NotesDbAdapter</code> at the top of the NoteEdit class:</p>
+ <pre>&nbsp;&nbsp;&nbsp; private NotesDbAdapter mDbHelper;</pre>
+<p>Also add an instance of <code>NotesDbAdapter</code> in the
+ <code>onCreate()</code> method (right below the <code>super.onCreate()</code> call):</p>
+ <pre>
+&nbsp;&nbsp;&nbsp; mDbHelper = new NotesDbAdapter(this);<br>
+&nbsp;&nbsp;&nbsp; mDbHelper.open();</pre>
+
+<h2>Step 3</h2>
+
+<p>In <code>NoteEdit</code>, we need to check the <var>savedInstanceState</var> for the
+<code>mRowId</code>, in case the note
+ editing contains a saved state in the Bundle, which we should recover (this would happen
+ if our Activity lost focus and then restarted).</p>
+ <ol>
+ <li>
+ Replace the code that currently initializes the <code>mRowId</code>:<br>
+ <pre>
+ mRowId = null;
+
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
+ }
+ </pre>
+ with this:
+ <pre>
+ mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
+ : null;
+ if (mRowId == null) {
+ Bundle extras = getIntent().getExtras();
+ mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
+ : null;
+ }
+ </pre>
+ </li>
+ <li>
+ Note the null check for <code>savedInstanceState</code>, and we still need to load up
+ <code>mRowId</code> from the <code>extras</code> Bundle if it is not
+ provided by the <code>savedInstanceState</code>. This is a ternary operator shorthand
+ to safely either use the value or null if it is not present.
+ </li>
+ </ol>
+
+<h2>Step 4</h2>
+
+<p>Next, we need to populate the fields based on the <code>mRowId</code> if we
+ have it:</p>
+ <pre>populateFields();</pre>
+ <p>This goes before the <code>confirmButton.setOnClickListener()</code> line.
+ We'll define this method in a moment.</p>
+
+<h2>Step 5</h2>
+
+<p>Get rid of the Bundle creation and Bundle value settings from the
+ <code>onClick()</code> handler method. The Activity no longer needs to
+ return any extra information to the caller. And because we no longer have
+ an Intent to return, we'll use the shorter version
+ of <code>setResult()</code>:</p>
+ <pre>
+public void onClick(View view) {
+ setResult(RESULT_OK);
+ finish();
+}</pre>
+ <p>We will take care of storing the updates or new notes in the database
+ ourselves, using the life-cycle methods.</p>
+
+ <p>The whole <code>onCreate()</code> method should now look like this:</p>
+ <pre>
+super.onCreate(savedInstanceState);
+
+mDbHelper = new NotesDbAdapter(this);
+mDbHelper.open();
+
+setContentView(R.layout.note_edit);
+
+mTitleText = (EditText) findViewById(R.id.title);
+mBodyText = (EditText) findViewById(R.id.body);
+
+Button confirmButton = (Button) findViewById(R.id.confirm);
+
+mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
+ : null;
+if (mRowId == null) {
+ Bundle extras = getIntent().getExtras();
+ mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
+ : null;
+}
+
+populateFields();
+
+confirmButton.setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View view) {
+ setResult(RESULT_OK);
+ finish();
+ }
+
+});</pre>
+
+<h2>Step 6</h2>
+
+<p>Define the <code>populateFields()</code> method.</p>
+ <pre>
+private void populateFields() {
+ if (mRowId != null) {
+ Cursor note = mDbHelper.fetchNote(mRowId);
+ startManagingCursor(note);
+ mTitleText.setText(note.getString(
+ note.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
+ mBodyText.setText(note.getString(
+ note.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
+ }
+}</pre>
+<p>This method uses the <code>NotesDbAdapter.fetchNote()</code> method to find the right note to
+edit, then it calls <code>startManagingCursor()</code> from the <code>Activity</code> class, which
+is an Android convenience method provided to take care of the Cursor life-cycle. This will release
+and re-create resources as dictated by the Activity life-cycle, so we don't need to worry about
+doing that ourselves. After that, we just look up the title and body values from the Cursor
+and populate the View elements with them.</p>
+
+
+<h2>Step 7</h2>
+
+ <div class="sidebox" style="border:2px solid #FFFFDD;float:right;
+ background-color:#FFFFEE;margin-right:0px;margin-bottom:.5em;
+ margin-top:1em;padding:0em;width:240px;">
+ <h2 style="border:0;font-size:12px;padding:.5em .5em .5em 1em;margin:0;
+ background-color:#FFFFDD;">Why handling life-cycle events is important</h2>
+ <p style="padding-left:.5em;font-size:12px;margin:0;
+ padding:.0em .5em .5em 1em;">If you are used to always having control in your applications, you
+ might not understand why all this life-cycle work is necessary. The reason
+ is that in Android, you are not in control of your Activity, the
+ operating system is!</p>
+ <p style="padding-left:.5em;font-size:12px;margin:0;
+ padding:.0em .5em .5em 1em;">As we have already seen, the Android model is based around activities
+ calling each other. When one Activity calls another, the current Activity
+ is paused at the very least, and may be killed altogether if the
+ system starts to run low on resources. If this happens, your Activity will
+ have to store enough state to come back up later, preferably in the same
+ state it was in when it was killed.</p>
+ <p style="padding-left:.5em;font-size:12px;margin:0;padding:.0em .5em .5em 1em;">
+ Android has a <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">well-defined life cycle</a>.
+ Lifecycle events can happen even if you are not handing off control to
+ another Activity explicitly. For example, perhaps a call comes in to the
+ handset. If this happens, and your Activity is running, it will be swapped
+ out while the call Activity takes over.</p>
+ </div>
+
+<p>Still in the <code>NoteEdit</code> class, we now override the methods
+ <code>onSaveInstanceState()</code>, <code>onPause()</code> and
+ <code>onResume()</code>. These are our life-cycle methods
+ (along with <code>onCreate()</code> which we already have).</p>
+
+<p><code>onSaveInstanceState()</code> is called by Android if the
+ Activity is being stopped and <strong>may be killed before it is
+ resumed!</strong> This means it should store any state necessary to
+ re-initialize to the same condition when the Activity is restarted. It is
+ the counterpart to the <code>onCreate()</code> method, and in fact the
+ <code>savedInstanceState</code> Bundle passed in to <code>onCreate()</code> is the same
+ Bundle that you construct as <code>outState</code> in the
+ <code>onSaveInstanceState()</code> method.</p>
+
+<p><code>onPause()</code> and <code>onResume()</code> are also
+ complimentary methods. <code>onPause()</code> is always called when the
+ Activity ends, even if we instigated that (with a <code>finish()</code> call for example).
+ We will use this to save the current note back to the database. Good
+ practice is to release any resources that can be released during an
+ <code>onPause()</code> as well, to take up less resources when in the
+ passive state. <code>onResume()</code> will call our <code>populateFields()</code> method
+ to read the note out of the database again and populate the fields.</p>
+
+<p>So, add some space after the <code>populateFields()</code> method
+ and add the following life-cycle methods:</p>
+ <ol type="a">
+ <li><code>
+ onSaveInstanceState()</code>:
+ <pre>
+ &#64;Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
+ }</pre>
+ </li>
+ <li><code>
+ onPause()</code>:
+ <pre>
+ &#64;Override
+ protected void onPause() {
+ super.onPause();
+ saveState();
+ }</pre>
+ <p>We'll define <code>saveState()</code> next.</p>
+ </li>
+ <li><code>
+ onResume()</code>:
+ <pre>
+ &#64;Override
+ protected void onResume() {
+ super.onResume();
+ populateFields();
+ }</pre>
+ </li>
+ </ol>
+
+
+<h2 style="clear:right;">Step 8</h2>
+
+<p>Define the <code>saveState()</code> method to put the data out to the
+database.</p>
+ <pre>
+ private void saveState() {
+ String title = mTitleText.getText().toString();
+ String body = mBodyText.getText().toString();
+
+ if (mRowId == null) {
+ long id = mDbHelper.createNote(title, body);
+ if (id > 0) {
+ mRowId = id;
+ }
+ } else {
+ mDbHelper.updateNote(mRowId, title, body);
+ }
+ }</pre>
+ <p>Note that we capture the return value from <code>createNote()</code> and if a valid row ID is
+ returned, we store it in the <code>mRowId</code> field so that we can update the note in future
+ rather than create a new one (which otherwise might happen if the life-cycle events are
+ triggered).</p>
+
+
+<h2 style="clear:right;">Step 9</h2>
+
+<p>Now pull out the previous handling code from the
+ <code>onActivityResult()</code> method in the <code>Notepadv3</code>
+ class.</p>
+<p>All of the note retrieval and updating now happens within the
+ <code>NoteEdit</code> life cycle, so all the <code>onActivityResult()</code>
+ method needs to do is update its view of the data, no other work is
+ necessary. The resulting method should look like this:</p>
+<pre>
+&#64;Override
+protected void onActivityResult(int requestCode, int resultCode,
+ Intent intent) {
+ super.onActivityResult(requestCode, resultCode, intent);
+ fillData();
+}</pre>
+
+<p>Because the other class now does the work, all this has to do is refresh
+ the data.</p>
+
+<h2>Step 10</h2>
+
+<p>Also remove the lines which set the title and body from the
+ <code>onListItemClick()</code> method (again they are no longer needed,
+ only the <code>mRowId</code> is):</p>
+<pre>
+ Cursor c = mNotesCursor;
+ c.moveToPosition(position);</pre>
+<br>
+and also remove:
+<br>
+<pre>
+ i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
+ c.getColumnIndex(NotesDbAdapter.KEY_TITLE)));
+ i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
+ c.getColumnIndex(NotesDbAdapter.KEY_BODY)));</pre>
+<br>
+so that all that should be left in that method is:
+<br>
+<pre>
+ super.onListItemClick(l, v, position, id);
+ Intent i = new Intent(this, NoteEdit.class);
+ i.putExtra(NotesDbAdapter.KEY_ROWID, id);
+ startActivityForResult(i, ACTIVITY_EDIT);</pre>
+
+ <p>You can also now remove the mNotesCursor field from the class, and set it back to using
+ a local variable in the <code>fillData()</code> method:
+<br><pre>
+ Cursor notesCursor = mDbHelper.fetchAllNotes();</pre></p>
+ <p>Note that the <code>m</code> in <code>mNotesCursor</code> denotes a member field, so when we
+ make <code>notesCursor</code> a local variable, we drop the <code>m</code>. Remember to rename the
+ other occurrences of <code>mNotesCursor</code> in your <code>fillData()</code> method.
+</ol>
+<p>
+Run it! (use <em>Run As -&gt; Android Application</em> on the project right
+click menu again)</p>
+
+<h2>Solution and Next Steps</h2>
+
+<p>You can see the solution to this exercise in <code>Notepadv3Solution</code>
+from
+the zip file to compare with your own.</p>
+<p>
+When you are ready, move on to the <a href="notepad-extra-credit.html">Tutorial
+Extra Credit</a> exercise, where you can use the Eclipse debugger to
+examine the life-cycle events as they happen.</p>