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 Activities have a <a 207 href="{@docRoot}guide/topics/fundamentals/activities.html#Lifecycle">well-defined life 208 cycle</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 @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 @Override 257 protected void onPause() { 258 super.onPause(); 259 saveState(); 260 }</pre> 261 </li> 262 <li><code> 263 onResume()</code>: 264 <pre> 265 @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> 273 and <code>onPause()</code> to ensure that the data is saved. This is because there is no 274 guarantee that <code>onSaveInstanceState()</code> will be called and because when it <em>is</em> 275 called, 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 281 database.</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 @Override 313 protected 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> 330 and 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> 338 so 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> 355 Run it! (use <em>Run As -> Android Application</em> on the project right 356 click 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> 361 from 362 the zip file to compare with your own.</p> 363 <p> 364 When you are ready, move on to the <a href="notepad-extra-credit.html">Tutorial 365 Extra Credit</a> exercise, where you can use the Eclipse debugger to 366 examine the life-cycle events as they happen.</p> 367