| 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) ? null : |
| (Long) savedInstanceState.getSerializable(NotesDbAdapter.KEY_ROWID); |
| 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> |
| <li> |
| Note the use of <code>Bundle.getSerializable()</code> instead of |
| <code>Bundle.getLong()</code>. The latter encoding returns a <code>long</code> primitive and |
| so can not be used to represent the case when <code>mRowId</code> is <code>null</code>. |
| </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) ? null : |
| (Long) savedInstanceState.getSerializable(NotesDbAdapter.KEY_ROWID); |
| 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-wrapper"> |
| <div class="sidebox"> |
| <h2>Why handling life-cycle events is important</h2> |
| <p>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>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> |
| Activities have a <a |
| href="{@docRoot}guide/components/activities.html#Lifecycle">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> |
| </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); |
| saveState(); |
| outState.putSerializable(NotesDbAdapter.KEY_ROWID, mRowId); |
| }</pre> |
| <p>We'll define <code>saveState()</code> next.</p> |
| </li> |
| <li><code> |
| onPause()</code>: |
| <pre> |
| @Override |
| protected void onPause() { |
| super.onPause(); |
| saveState(); |
| }</pre> |
| </li> |
| <li><code> |
| onResume()</code>: |
| <pre> |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| populateFields(); |
| }</pre> |
| </li> |
| </ol> |
| <p>Note that <code>saveState()</code> must be called in both <code>onSaveInstanceState()</code> |
| and <code>onPause()</code> to ensure that the data is saved. This is because there is no |
| guarantee that <code>onSaveInstanceState()</code> will be called and because when it <em>is</em> |
| called, it is called before <code>onPause()</code>.</p> |
| |
| |
| <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> |