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