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