Home | History | Annotate | Download | only in notepad
      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> &gt;
     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 &mdash; 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>&nbsp;&nbsp;&nbsp; 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 &nbsp;&nbsp;&nbsp; mDbHelper = new NotesDbAdapter(this);<br>
     69 &nbsp;&nbsp;&nbsp; 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     &#64;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     &#64;Override
    256     protected void onPause() {
    257         super.onPause();
    258         saveState();
    259     }</pre>
    260     </li>
    261     <li><code>
    262       onResume()</code>:
    263       <pre>
    264     &#64;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 &#64;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 -&gt; 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