diff options
Diffstat (limited to 'docs/html/resources/tutorials/notepad/notepad-ex3.jd')
-rw-r--r-- | docs/html/resources/tutorials/notepad/notepad-ex3.jd | 358 |
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> > +<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 — 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> 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> + mDbHelper = new NotesDbAdapter(this);<br> + 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> + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putLong(NotesDbAdapter.KEY_ROWID, mRowId); + }</pre> + </li> + <li><code> + onPause()</code>: + <pre> + @Override + protected void onPause() { + super.onPause(); + saveState(); + }</pre> + <p>We'll define <code>saveState()</code> next.</p> + </li> + <li><code> + onResume()</code>: + <pre> + @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> +@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 -> 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> |