1excludeFromSuggestions=true
2page.title=Notepad Exercise 3
3parent.title=Notepad Tutorial
4parent.link=index.html
5@jd:body
6
7
8<p><em>In this exercise, you will use life-cycle event callbacks to store and
9retrieve application state data. This exercise demonstrates:</em></p>
10<ul>
11<li><em>Life-cycle events and how your application can use them</em></li>
12<li><em>Techniques for maintaining application state</em></li>
13</ul>
14
15<div style="float:right;white-space:nowrap">
16	[<a href="notepad-ex1.html">Exercise 1</a>]
17	[<a href="notepad-ex2.html">Exercise 2</a>]
18	<span style="color:#BBB;">
19		[<a href="notepad-ex3.html" style="color:#BBB;">Exercise 3</a>]
20	</span>
21	[<a href="notepad-extra-credit.html">Extra Credit</a>]
22</div>
23
24<h2>Step 1</h2>
25
26<p>Import <code>Notepadv3</code> into Eclipse. If you see an error about
27<code>AndroidManifest.xml,</code> or some problems related to an Android zip
28file, right click on the project and select <strong>Android Tools</strong> &gt;
29<strong>Fix Project Properties</strong> from the popup menu. The starting point for this exercise is
30exactly where we left off at the end of the Notepadv2. </p>
31<p>The current application has some problems &mdash; hitting the back button when editing
32causes a crash, and anything else that happens during editing will cause the
33edits to be lost.</p>
34<p>To fix this, we will move most of the functionality for creating and editing
35the note into the NoteEdit class, and introduce a full life cycle for editing
36notes.</p>
37
38  <ol>
39    <li>Remove the code in <code>NoteEdit</code> that parses the title and body
40    from the extras Bundle.
41    <p>Instead, we are going to use the <code>DBHelper</code> class
42    to access the notes from the database directly. All we need passed into the
43    NoteEdit Activity is a <code>mRowId</code> (but only if we are editing, if creating we pass
44    nothing). Remove these lines:</p>
45      <pre>
46String title = extras.getString(NotesDbAdapter.KEY_TITLE);
47String body = extras.getString(NotesDbAdapter.KEY_BODY);</pre>
48    </li>
49    <li>We will also get rid of the properties that were being passed in
50    the <code>extras</code> Bundle, which we were using to set the title
51    and body text edit values in the UI. So delete:
52    <pre>
53if (title != null) {
54    mTitleText.setText(title);
55}
56if (body != null) {
57    mBodyText.setText(body);
58}</pre>
59    </li>
60  </ol>
61
62<h2>Step 2</h2>
63
64<p>Create a class field for a <code>NotesDbAdapter</code> at the top of the NoteEdit class:</p>
65    <pre>&nbsp;&nbsp;&nbsp; private NotesDbAdapter mDbHelper;</pre>
66<p>Also add an instance of <code>NotesDbAdapter</code> in the
67    <code>onCreate()</code> method (right below the <code>super.onCreate()</code> call):</p>
68    <pre>
69&nbsp;&nbsp;&nbsp; mDbHelper = new NotesDbAdapter(this);<br>
70&nbsp;&nbsp;&nbsp; mDbHelper.open();</pre>
71
72<h2>Step 3</h2>
73
74<p>In <code>NoteEdit</code>, we need to check the <var>savedInstanceState</var> for the
75<code>mRowId</code>, in case the note
76    editing contains a saved state in the Bundle, which we should recover (this would happen
77  if our Activity lost focus and then restarted).</p>
78  <ol>
79    <li>
80      Replace the code that currently initializes the <code>mRowId</code>:<br>
81      <pre>
82        mRowId = null;
83
84        Bundle extras = getIntent().getExtras();
85        if (extras != null) {
86            mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
87        }
88        </pre>
89        with this:
90        <pre>
91        mRowId = (savedInstanceState == null) ? null :
92            (Long) savedInstanceState.getSerializable(NotesDbAdapter.KEY_ROWID);
93        if (mRowId == null) {
94            Bundle extras = getIntent().getExtras();
95            mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
96                                    : null;
97        }
98        </pre>
99    </li>
100    <li>
101      Note the null check for <code>savedInstanceState</code>, and we still need to load up
102      <code>mRowId</code> from the <code>extras</code> Bundle if it is not
103      provided by the <code>savedInstanceState</code>. This is a ternary operator shorthand
104      to safely either use the value or null if it is not present.
105    </li>
106    <li>
107      Note the use of <code>Bundle.getSerializable()</code> instead of
108      <code>Bundle.getLong()</code>.  The latter encoding returns a <code>long</code> primitive and
109      so can not be used to represent the case when <code>mRowId</code> is <code>null</code>.
110    </li>
111  </ol>
112
113<h2>Step 4</h2>
114
115<p>Next, we need to populate the fields based on the <code>mRowId</code> if we
116    have it:</p>
117    <pre>populateFields();</pre>
118    <p>This goes before the <code>confirmButton.setOnClickListener()</code> line.
119    We'll define this method in a moment.</p>
120
121<h2>Step 5</h2>
122
123<p>Get rid of the Bundle creation and Bundle value settings from the
124    <code>onClick()</code> handler method. The Activity no longer needs to
125    return any extra information to the caller. And because we no longer have
126    an Intent to return, we'll use the shorter version
127    of <code>setResult()</code>:</p>
128    <pre>
129public void onClick(View view) {
130    setResult(RESULT_OK);
131    finish();
132}</pre>
133    <p>We will take care of storing the updates or new notes in the database
134    ourselves, using the life-cycle methods.</p>
135
136    <p>The whole <code>onCreate()</code> method should now look like this:</p>
137    <pre>
138super.onCreate(savedInstanceState);
139
140mDbHelper = new NotesDbAdapter(this);
141mDbHelper.open();
142
143setContentView(R.layout.note_edit);
144
145mTitleText = (EditText) findViewById(R.id.title);
146mBodyText = (EditText) findViewById(R.id.body);
147
148Button confirmButton = (Button) findViewById(R.id.confirm);
149
150mRowId = (savedInstanceState == null) ? null :
151    (Long) savedInstanceState.getSerializable(NotesDbAdapter.KEY_ROWID);
152if (mRowId == null) {
153    Bundle extras = getIntent().getExtras();
154    mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
155                            : null;
156}
157
158populateFields();
159
160confirmButton.setOnClickListener(new View.OnClickListener() {
161
162    public void onClick(View view) {
163        setResult(RESULT_OK);
164        finish();
165    }
166
167});</pre>
168
169<h2>Step 6</h2>
170
171<p>Define the <code>populateFields()</code> method.</p>
172    <pre>
173private void populateFields() {
174    if (mRowId != null) {
175        Cursor note = mDbHelper.fetchNote(mRowId);
176        startManagingCursor(note);
177        mTitleText.setText(note.getString(
178	            note.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
179        mBodyText.setText(note.getString(
180                note.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
181    }
182}</pre>
183<p>This method uses the <code>NotesDbAdapter.fetchNote()</code> method to find the right note to
184edit, then it calls <code>startManagingCursor()</code> from the <code>Activity</code> class, which
185is an Android convenience method provided to take care of the Cursor life-cycle. This will release
186and re-create resources as dictated by the Activity life-cycle, so we don't need to worry about
187doing that ourselves. After that, we just look up the title and body values from the Cursor
188and populate the View elements with them.</p>
189
190
191<h2>Step 7</h2>
192
193  <div class="sidebox-wrapper">
194  <div class="sidebox">
195    <h2>Why handling life-cycle events is important</h2>
196    <p>If you are used to always having control in your applications, you
197    might not understand why all this life-cycle work is necessary. The reason
198    is that in Android, you are not in control of your Activity, the
199    operating system is!</p>
200    <p>As we have already seen, the Android model is based around activities
201    calling each other. When one Activity calls another, the current Activity
202    is paused at the very least, and may be killed altogether if the
203    system starts to run low on resources. If this happens, your Activity will
204    have to store enough state to come back up later, preferably in the same
205    state it was in when it was killed.</p>
206    <p>
207    Activities have a <a
208href="{@docRoot}guide/components/activities.html#Lifecycle">well-defined life
209cycle</a>.
210    Lifecycle events can happen even if you are not handing off control to
211    another Activity explicitly. For example, perhaps a call comes in to the
212    handset. If this happens, and your Activity is running, it will be swapped
213    out while the call Activity takes over.</p>
214  </div>
215  </div>
216
217<p>Still in the <code>NoteEdit</code> class, we now override the methods
218   <code>onSaveInstanceState()</code>, <code>onPause()</code> and
219   <code>onResume()</code>. These are our life-cycle methods
220   (along with <code>onCreate()</code> which we already have).</p>
221
222<p><code>onSaveInstanceState()</code> is called by Android if the
223    Activity is being stopped and <strong>may be killed before it is
224    resumed!</strong> This means it should store any state necessary to
225    re-initialize to the same condition when the Activity is restarted. It is
226    the counterpart to the <code>onCreate()</code> method, and in fact the
227    <code>savedInstanceState</code> Bundle passed in to <code>onCreate()</code> is the same
228    Bundle that you construct as <code>outState</code> in the
229    <code>onSaveInstanceState()</code> method.</p>
230
231<p><code>onPause()</code> and <code>onResume()</code> are also
232    complimentary methods. <code>onPause()</code> is always called when the
233    Activity ends, even if we instigated that (with a <code>finish()</code> call for example).
234    We will use this to save the current note back to the database. Good
235    practice is to release any resources that can be released during an
236    <code>onPause()</code> as well, to take up less resources when in the
237    passive state. <code>onResume()</code> will call our <code>populateFields()</code> method
238    to read the note out of the database again and populate the fields.</p>
239
240<p>So, add some space after the <code>populateFields()</code> method
241  and add the following life-cycle methods:</p>
242  <ol type="a">
243    <li><code>
244      onSaveInstanceState()</code>:
245      <pre>
246    &#64;Override
247    protected void onSaveInstanceState(Bundle outState) {
248        super.onSaveInstanceState(outState);
249        saveState();
250        outState.putSerializable(NotesDbAdapter.KEY_ROWID, mRowId);
251    }</pre>
252    <p>We'll define <code>saveState()</code> next.</p>
253    </li>
254    <li><code>
255      onPause()</code>:
256      <pre>
257    &#64;Override
258    protected void onPause() {
259        super.onPause();
260        saveState();
261    }</pre>
262    </li>
263    <li><code>
264      onResume()</code>:
265      <pre>
266    &#64;Override
267    protected void onResume() {
268        super.onResume();
269        populateFields();
270    }</pre>
271    </li>
272  </ol>
273<p>Note that <code>saveState()</code> must be called in both <code>onSaveInstanceState()</code>
274and <code>onPause()</code> to ensure that the data is saved.  This is because there is no
275guarantee that <code>onSaveInstanceState()</code> will be called and because when it <em>is</em>
276called, it is called before <code>onPause()</code>.</p>
277
278
279<h2 style="clear:right;">Step 8</h2>
280
281<p>Define the <code>saveState()</code> method to put the data out to the
282database.</p>
283    <pre>
284     private void saveState() {
285        String title = mTitleText.getText().toString();
286        String body = mBodyText.getText().toString();
287
288        if (mRowId == null) {
289            long id = mDbHelper.createNote(title, body);
290            if (id > 0) {
291                mRowId = id;
292            }
293        } else {
294            mDbHelper.updateNote(mRowId, title, body);
295        }
296    }</pre>
297  <p>Note that we capture the return value from <code>createNote()</code> and if a valid row ID is
298  returned, we store it in the <code>mRowId</code> field so that we can update the note in future
299  rather than create a new one (which otherwise might happen if the life-cycle events are
300  triggered).</p>
301
302
303<h2 style="clear:right;">Step 9</h2>
304
305<p>Now pull out the previous handling code from the
306    <code>onActivityResult()</code> method in the <code>Notepadv3</code>
307    class.</p>
308<p>All of the note retrieval and updating now happens within the
309    <code>NoteEdit</code> life cycle, so all the <code>onActivityResult()</code>
310    method needs to do is update its view of the data, no other work is
311    necessary. The resulting method should look like this:</p>
312<pre>
313&#64;Override
314protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
315    super.onActivityResult(requestCode, resultCode, intent);
316    fillData();
317}</pre>
318
319<p>Because the other class now does the work, all this has to do is refresh
320      the data.</p>
321
322<h2>Step 10</h2>
323
324<p>Also remove the lines which set the title and body from the
325    <code>onListItemClick()</code> method (again they are no longer needed,
326    only the <code>mRowId</code> is):</p>
327<pre>
328    Cursor c = mNotesCursor;
329    c.moveToPosition(position);</pre>
330<br>
331and also remove:
332<br>
333<pre>
334    i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
335                    c.getColumnIndex(NotesDbAdapter.KEY_TITLE)));
336    i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
337                    c.getColumnIndex(NotesDbAdapter.KEY_BODY)));</pre>
338<br>
339so that all that should be left in that method is:
340<br>
341<pre>
342    super.onListItemClick(l, v, position, id);
343    Intent i = new Intent(this, NoteEdit.class);
344    i.putExtra(NotesDbAdapter.KEY_ROWID, id);
345    startActivityForResult(i, ACTIVITY_EDIT);</pre>
346
347  <p>You can also now remove the mNotesCursor field from the class, and set it back to using
348  a local variable in the <code>fillData()</code> method:
349<br><pre>
350    Cursor notesCursor = mDbHelper.fetchAllNotes();</pre></p>
351  <p>Note that the <code>m</code> in <code>mNotesCursor</code> denotes a member field, so when we
352  make <code>notesCursor</code> a local variable, we drop the <code>m</code>. Remember to rename the
353  other occurrences of <code>mNotesCursor</code> in your <code>fillData()</code> method.
354</ol>
355<p>
356Run it! (use <em>Run As -&gt; Android Application</em> on the project right
357click menu again)</p>
358
359<h2>Solution and Next Steps</h2>
360
361<p>You can see the solution to this exercise in <code>Notepadv3Solution</code>
362from
363the zip file to compare with your own.</p>
364<p>
365When you are ready, move on to the <a href="notepad-extra-credit.html">Tutorial
366Extra Credit</a> exercise, where you can use the Eclipse debugger to
367examine the life-cycle events as they happen.</p>
368