summaryrefslogtreecommitdiffstats
path: root/docs/html/training/notepad/notepad-ex3.jd
blob: e31ecda2e20b871197963696d51180cdd4c2d975 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
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> &gt;
<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 &mdash; 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>&nbsp;&nbsp;&nbsp; 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>
&nbsp;&nbsp;&nbsp; mDbHelper = new NotesDbAdapter(this);<br>
&nbsp;&nbsp;&nbsp; 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>
    &#64;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>
    &#64;Override
    protected void onPause() {
        super.onPause();
        saveState();
    }</pre>
    </li>
    <li><code>
      onResume()</code>:
      <pre>
    &#64;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>
&#64;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 -&gt; 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>