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> > 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 — 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> 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 ? 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 @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 @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 @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@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 -> 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