• 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 ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
91                                            : null;
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  </ol>
106
107<h2>Step 4</h2>
108
109<p>Next, we need to populate the fields based on the <code>mRowId</code> if we
110    have it:</p>
111    <pre>populateFields();</pre>
112    <p>This goes before the <code>confirmButton.setOnClickListener()</code> line.
113    We'll define this method in a moment.</p>
114
115<h2>Step 5</h2>
116
117<p>Get rid of the Bundle creation and Bundle value settings from the
118    <code>onClick()</code> handler method. The Activity no longer needs to
119    return any extra information to the caller. And because we no longer have
120    an Intent to return, we'll use the shorter version
121    of <code>setResult()</code>:</p>
122    <pre>
123public void onClick(View view) {
124    setResult(RESULT_OK);
125    finish();
126}</pre>
127    <p>We will take care of storing the updates or new notes in the database
128    ourselves, using the life-cycle methods.</p>
129
130    <p>The whole <code>onCreate()</code> method should now look like this:</p>
131    <pre>
132super.onCreate(savedInstanceState);
133
134mDbHelper = new NotesDbAdapter(this);
135mDbHelper.open();
136
137setContentView(R.layout.note_edit);
138
139mTitleText = (EditText) findViewById(R.id.title);
140mBodyText = (EditText) findViewById(R.id.body);
141
142Button confirmButton = (Button) findViewById(R.id.confirm);
143
144mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
145                                    : null;
146if (mRowId == null) {
147    Bundle extras = getIntent().getExtras();
148    mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
149                            : null;
150}
151
152populateFields();
153
154confirmButton.setOnClickListener(new View.OnClickListener() {
155
156    public void onClick(View view) {
157        setResult(RESULT_OK);
158        finish();
159    }
160
161});</pre>
162
163<h2>Step 6</h2>
164
165<p>Define the <code>populateFields()</code> method.</p>
166    <pre>
167private void populateFields() {
168    if (mRowId != null) {
169        Cursor note = mDbHelper.fetchNote(mRowId);
170        startManagingCursor(note);
171        mTitleText.setText(note.getString(
172	            note.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
173        mBodyText.setText(note.getString(
174                note.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
175    }
176}</pre>
177<p>This method uses the <code>NotesDbAdapter.fetchNote()</code> method to find the right note to
178edit, then it calls <code>startManagingCursor()</code> from the <code>Activity</code> class, which
179is an Android convenience method provided to take care of the Cursor life-cycle. This will release
180and re-create resources as dictated by the Activity life-cycle, so we don't need to worry about
181doing that ourselves. After that, we just look up the title and body values from the Cursor
182and populate the View elements with them.</p>
183
184
185<h2>Step 7</h2>
186
187    <div class="sidebox" style="border:2px solid #FFFFDD;float:right;
188      background-color:#FFFFEE;margin-right:0px;margin-bottom:.5em;
189      margin-top:1em;padding:0em;width:240px;">
190      <h2 style="border:0;font-size:12px;padding:.5em .5em .5em 1em;margin:0;
191      background-color:#FFFFDD;">Why handling life-cycle events is important</h2>
192      <p style="padding-left:.5em;font-size:12px;margin:0;
193     padding:.0em .5em .5em 1em;">If you are used to always having control in your applications, you
194      might not understand why all this life-cycle work is necessary. The reason
195      is that in Android, you are not in control of your Activity, the
196      operating system is!</p>
197      <p style="padding-left:.5em;font-size:12px;margin:0;
198     padding:.0em .5em .5em 1em;">As we have already seen, the Android model is based around activities
199      calling each other. When one Activity calls another, the current Activity
200      is paused at the very least, and may be killed altogether if the
201      system starts to run low on resources. If this happens, your Activity will
202      have to store enough state to come back up later, preferably in the same
203      state it was in when it was killed.</p>
204      <p style="padding-left:.5em;font-size:12px;margin:0;padding:.0em .5em .5em 1em;">
205      Android has a <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">well-defined life cycle</a>.
206      Lifecycle events can happen even if you are not handing off control to
207      another Activity explicitly. For example, perhaps a call comes in to the
208      handset. If this happens, and your Activity is running, it will be swapped
209      out while the call Activity takes over.</p>
210    </div>
211
212<p>Still in the <code>NoteEdit</code> class, we now override the methods
213   <code>onSaveInstanceState()</code>, <code>onPause()</code> and
214   <code>onResume()</code>. These are our life-cycle methods
215   (along with <code>onCreate()</code> which we already have).</p>
216
217<p><code>onSaveInstanceState()</code> is called by Android if the
218    Activity is being stopped and <strong>may be killed before it is
219    resumed!</strong> This means it should store any state necessary to
220    re-initialize to the same condition when the Activity is restarted. It is
221    the counterpart to the <code>onCreate()</code> method, and in fact the
222    <code>savedInstanceState</code> Bundle passed in to <code>onCreate()</code> is the same
223    Bundle that you construct as <code>outState</code> in the
224    <code>onSaveInstanceState()</code> method.</p>
225
226<p><code>onPause()</code> and <code>onResume()</code> are also
227    complimentary methods. <code>onPause()</code> is always called when the
228    Activity ends, even if we instigated that (with a <code>finish()</code> call for example).
229    We will use this to save the current note back to the database. Good
230    practice is to release any resources that can be released during an
231    <code>onPause()</code> as well, to take up less resources when in the
232    passive state. <code>onResume()</code> will call our <code>populateFields()</code> method
233    to read the note out of the database again and populate the fields.</p>
234
235<p>So, add some space after the <code>populateFields()</code> method
236  and add the following life-cycle methods:</p>
237  <ol type="a">
238    <li><code>
239      onSaveInstanceState()</code>:
240      <pre>
241    &#64;Override
242    protected void onSaveInstanceState(Bundle outState) {
243        super.onSaveInstanceState(outState);
244        outState.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
245    }</pre>
246    </li>
247    <li><code>
248      onPause()</code>:
249      <pre>
250    &#64;Override
251    protected void onPause() {
252        super.onPause();
253        saveState();
254    }</pre>
255    <p>We'll define <code>saveState()</code> next.</p>
256    </li>
257    <li><code>
258      onResume()</code>:
259      <pre>
260    &#64;Override
261    protected void onResume() {
262        super.onResume();
263        populateFields();
264    }</pre>
265    </li>
266  </ol>
267
268
269<h2 style="clear:right;">Step 8</h2>
270
271<p>Define the <code>saveState()</code> method to put the data out to the
272database.</p>
273    <pre>
274     private void saveState() {
275        String title = mTitleText.getText().toString();
276        String body = mBodyText.getText().toString();
277
278        if (mRowId == null) {
279            long id = mDbHelper.createNote(title, body);
280            if (id > 0) {
281                mRowId = id;
282            }
283        } else {
284            mDbHelper.updateNote(mRowId, title, body);
285        }
286    }</pre>
287  <p>Note that we capture the return value from <code>createNote()</code> and if a valid row ID is
288  returned, we store it in the <code>mRowId</code> field so that we can update the note in future
289  rather than create a new one (which otherwise might happen if the life-cycle events are
290  triggered).</p>
291
292
293<h2 style="clear:right;">Step 9</h2>
294
295<p>Now pull out the previous handling code from the
296    <code>onActivityResult()</code> method in the <code>Notepadv3</code>
297    class.</p>
298<p>All of the note retrieval and updating now happens within the
299    <code>NoteEdit</code> life cycle, so all the <code>onActivityResult()</code>
300    method needs to do is update its view of the data, no other work is
301    necessary. The resulting method should look like this:</p>
302<pre>
303&#64;Override
304protected void onActivityResult(int requestCode, int resultCode,
305                                Intent intent) {
306    super.onActivityResult(requestCode, resultCode, intent);
307    fillData();
308}</pre>
309
310<p>Because the other class now does the work, all this has to do is refresh
311      the data.</p>
312
313<h2>Step 10</h2>
314
315<p>Also remove the lines which set the title and body from the
316    <code>onListItemClick()</code> method (again they are no longer needed,
317    only the <code>mRowId</code> is):</p>
318<pre>
319    Cursor c = mNotesCursor;
320    c.moveToPosition(position);</pre>
321<br>
322and also remove:
323<br>
324<pre>
325    i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
326                    c.getColumnIndex(NotesDbAdapter.KEY_TITLE)));
327    i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
328                    c.getColumnIndex(NotesDbAdapter.KEY_BODY)));</pre>
329<br>
330so that all that should be left in that method is:
331<br>
332<pre>
333    super.onListItemClick(l, v, position, id);
334    Intent i = new Intent(this, NoteEdit.class);
335    i.putExtra(NotesDbAdapter.KEY_ROWID, id);
336    startActivityForResult(i, ACTIVITY_EDIT);</pre>
337
338  <p>You can also now remove the mNotesCursor field from the class, and set it back to using
339  a local variable in the <code>fillData()</code> method:
340<br><pre>
341    Cursor notesCursor = mDbHelper.fetchAllNotes();</pre></p>
342  <p>Note that the <code>m</code> in <code>mNotesCursor</code> denotes a member field, so when we
343  make <code>notesCursor</code> a local variable, we drop the <code>m</code>. Remember to rename the
344  other occurrences of <code>mNotesCursor</code> in your <code>fillData()</code> method.
345</ol>
346<p>
347Run it! (use <em>Run As -&gt; Android Application</em> on the project right
348click menu again)</p>
349
350<h2>Solution and Next Steps</h2>
351
352<p>You can see the solution to this exercise in <code>Notepadv3Solution</code>
353from
354the zip file to compare with your own.</p>
355<p>
356When you are ready, move on to the <a href="notepad-extra-credit.html">Tutorial
357Extra Credit</a> exercise, where you can use the Eclipse debugger to
358examine the life-cycle events as they happen.</p>
359