1 page.title=Notepad Exercise 3 2 parent.title=Notepad Tutorial 3 parent.link=index.html 4 @jd:body 5 6 7 <p><em>In this exercise, you will use life-cycle event callbacks to store and 8 retrieve 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 27 file, right click on the project and select <strong>Android Tools</strong> > 28 <strong>Fix Project Properties</strong> from the popup menu. The starting point for this exercise is 29 exactly where we left off at the end of the Notepadv2. </p> 30 <p>The current application has some problems — hitting the back button when editing 31 causes a crash, and anything else that happens during editing will cause the 32 edits to be lost.</p> 33 <p>To fix this, we will move most of the functionality for creating and editing 34 the note into the NoteEdit class, and introduce a full life cycle for editing 35 notes.</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> 45 String title = extras.getString(NotesDbAdapter.KEY_TITLE); 46 String 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> 52 if (title != null) { 53 mTitleText.setText(title); 54 } 55 if (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> 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 mDbHelper = new NotesDbAdapter(this);<br> 69 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> 128 public 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> 137 super.onCreate(savedInstanceState); 138 139 mDbHelper = new NotesDbAdapter(this); 140 mDbHelper.open(); 141 142 setContentView(R.layout.note_edit); 143 144 mTitleText = (EditText) findViewById(R.id.title); 145 mBodyText = (EditText) findViewById(R.id.body); 146 147 Button confirmButton = (Button) findViewById(R.id.confirm); 148 149 mRowId = (savedInstanceState == null) ? null : 150 (Long) savedInstanceState.getSerializable(NotesDbAdapter.KEY_ROWID); 151 if (mRowId == null) { 152 Bundle extras = getIntent().getExtras(); 153 mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID) 154 : null; 155 } 156 157 populateFields(); 158 159 confirmButton.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> 172 private 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 183 edit, then it calls <code>startManagingCursor()</code> from the <code>Activity</code> class, which 184 is an Android convenience method provided to take care of the Cursor life-cycle. This will release 185 and re-create resources as dictated by the Activity life-cycle, so we don't need to worry about 186 doing that ourselves. After that, we just look up the title and body values from the Cursor 187 and 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 Android has a <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">well-defined life 207 cycle</a>. 208 Lifecycle events can happen even if you are not handing off control to 209 another Activity explicitly. For example, perhaps a call comes in to the 210 handset. If this happens, and your Activity is running, it will be swapped 211 out while the call Activity takes over.</p> 212 </div> 213 </div> 214 215 <p>Still in the <code>NoteEdit</code> class, we now override the methods 216 <code>onSaveInstanceState()</code>, <code>onPause()</code> and 217 <code>onResume()</code>. These are our life-cycle methods 218 (along with <code>onCreate()</code> which we already have).</p> 219 220 <p><code>onSaveInstanceState()</code> is called by Android if the 221 Activity is being stopped and <strong>may be killed before it is 222 resumed!</strong> This means it should store any state necessary to 223 re-initialize to the same condition when the Activity is restarted. It is 224 the counterpart to the <code>onCreate()</code> method, and in fact the 225 <code>savedInstanceState</code> Bundle passed in to <code>onCreate()</code> is the same 226 Bundle that you construct as <code>outState</code> in the 227 <code>onSaveInstanceState()</code> method.</p> 228 229 <p><code>onPause()</code> and <code>onResume()</code> are also 230 complimentary methods. <code>onPause()</code> is always called when the 231 Activity ends, even if we instigated that (with a <code>finish()</code> call for example). 232 We will use this to save the current note back to the database. Good 233 practice is to release any resources that can be released during an 234 <code>onPause()</code> as well, to take up less resources when in the 235 passive state. <code>onResume()</code> will call our <code>populateFields()</code> method 236 to read the note out of the database again and populate the fields.</p> 237 238 <p>So, add some space after the <code>populateFields()</code> method 239 and add the following life-cycle methods:</p> 240 <ol type="a"> 241 <li><code> 242 onSaveInstanceState()</code>: 243 <pre> 244 @Override 245 protected void onSaveInstanceState(Bundle outState) { 246 super.onSaveInstanceState(outState); 247 saveState(); 248 outState.putSerializable(NotesDbAdapter.KEY_ROWID, mRowId); 249 }</pre> 250 <p>We'll define <code>saveState()</code> next.</p> 251 </li> 252 <li><code> 253 onPause()</code>: 254 <pre> 255 @Override 256 protected void onPause() { 257 super.onPause(); 258 saveState(); 259 }</pre> 260 </li> 261 <li><code> 262 onResume()</code>: 263 <pre> 264 @Override 265 protected void onResume() { 266 super.onResume(); 267 populateFields(); 268 }</pre> 269 </li> 270 </ol> 271 <p>Note that <code>saveState()</code> must be called in both <code>onSaveInstanceState()</code> 272 and <code>onPause()</code> to ensure that the data is saved. This is because there is no 273 guarantee that <code>onSaveInstanceState()</code> will be called and because when it <em>is</em> 274 called, it is called before <code>onPause()</code>.</p> 275 276 277 <h2 style="clear:right;">Step 8</h2> 278 279 <p>Define the <code>saveState()</code> method to put the data out to the 280 database.</p> 281 <pre> 282 private void saveState() { 283 String title = mTitleText.getText().toString(); 284 String body = mBodyText.getText().toString(); 285 286 if (mRowId == null) { 287 long id = mDbHelper.createNote(title, body); 288 if (id > 0) { 289 mRowId = id; 290 } 291 } else { 292 mDbHelper.updateNote(mRowId, title, body); 293 } 294 }</pre> 295 <p>Note that we capture the return value from <code>createNote()</code> and if a valid row ID is 296 returned, we store it in the <code>mRowId</code> field so that we can update the note in future 297 rather than create a new one (which otherwise might happen if the life-cycle events are 298 triggered).</p> 299 300 301 <h2 style="clear:right;">Step 9</h2> 302 303 <p>Now pull out the previous handling code from the 304 <code>onActivityResult()</code> method in the <code>Notepadv3</code> 305 class.</p> 306 <p>All of the note retrieval and updating now happens within the 307 <code>NoteEdit</code> life cycle, so all the <code>onActivityResult()</code> 308 method needs to do is update its view of the data, no other work is 309 necessary. The resulting method should look like this:</p> 310 <pre> 311 @Override 312 protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 313 super.onActivityResult(requestCode, resultCode, intent); 314 fillData(); 315 }</pre> 316 317 <p>Because the other class now does the work, all this has to do is refresh 318 the data.</p> 319 320 <h2>Step 10</h2> 321 322 <p>Also remove the lines which set the title and body from the 323 <code>onListItemClick()</code> method (again they are no longer needed, 324 only the <code>mRowId</code> is):</p> 325 <pre> 326 Cursor c = mNotesCursor; 327 c.moveToPosition(position);</pre> 328 <br> 329 and also remove: 330 <br> 331 <pre> 332 i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString( 333 c.getColumnIndex(NotesDbAdapter.KEY_TITLE))); 334 i.putExtra(NotesDbAdapter.KEY_BODY, c.getString( 335 c.getColumnIndex(NotesDbAdapter.KEY_BODY)));</pre> 336 <br> 337 so that all that should be left in that method is: 338 <br> 339 <pre> 340 super.onListItemClick(l, v, position, id); 341 Intent i = new Intent(this, NoteEdit.class); 342 i.putExtra(NotesDbAdapter.KEY_ROWID, id); 343 startActivityForResult(i, ACTIVITY_EDIT);</pre> 344 345 <p>You can also now remove the mNotesCursor field from the class, and set it back to using 346 a local variable in the <code>fillData()</code> method: 347 <br><pre> 348 Cursor notesCursor = mDbHelper.fetchAllNotes();</pre></p> 349 <p>Note that the <code>m</code> in <code>mNotesCursor</code> denotes a member field, so when we 350 make <code>notesCursor</code> a local variable, we drop the <code>m</code>. Remember to rename the 351 other occurrences of <code>mNotesCursor</code> in your <code>fillData()</code> method. 352 </ol> 353 <p> 354 Run it! (use <em>Run As -> Android Application</em> on the project right 355 click menu again)</p> 356 357 <h2>Solution and Next Steps</h2> 358 359 <p>You can see the solution to this exercise in <code>Notepadv3Solution</code> 360 from 361 the zip file to compare with your own.</p> 362 <p> 363 When you are ready, move on to the <a href="notepad-extra-credit.html">Tutorial 364 Extra Credit</a> exercise, where you can use the Eclipse debugger to 365 examine the life-cycle events as they happen.</p> 366