Home | History | Annotate | Download | only in notepad
      1 excludeFromSuggestions=true
      2 page.title=Notepad Exercise 3
      3 parent.title=Notepad Tutorial
      4 parent.link=index.html
      5 @jd:body
      6 
      7 
      8 <p><em>In this exercise, you will use life-cycle event callbacks to store and
      9 retrieve 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
     28 file, right click on the project and select <strong>Android Tools</strong> &gt;
     29 <strong>Fix Project Properties</strong> from the popup menu. The starting point for this exercise is
     30 exactly where we left off at the end of the Notepadv2. </p>
     31 <p>The current application has some problems &mdash; hitting the back button when editing
     32 causes a crash, and anything else that happens during editing will cause the
     33 edits to be lost.</p>
     34 <p>To fix this, we will move most of the functionality for creating and editing
     35 the note into the NoteEdit class, and introduce a full life cycle for editing
     36 notes.</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>
     46 String title = extras.getString(NotesDbAdapter.KEY_TITLE);
     47 String 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>
     53 if (title != null) {
     54     mTitleText.setText(title);
     55 }
     56 if (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>&nbsp;&nbsp;&nbsp; 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 &nbsp;&nbsp;&nbsp; mDbHelper = new NotesDbAdapter(this);<br>
     70 &nbsp;&nbsp;&nbsp; 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>
    129 public 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>
    138 super.onCreate(savedInstanceState);
    139 
    140 mDbHelper = new NotesDbAdapter(this);
    141 mDbHelper.open();
    142 
    143 setContentView(R.layout.note_edit);
    144 
    145 mTitleText = (EditText) findViewById(R.id.title);
    146 mBodyText = (EditText) findViewById(R.id.body);
    147 
    148 Button confirmButton = (Button) findViewById(R.id.confirm);
    149 
    150 mRowId = (savedInstanceState == null) ? null :
    151     (Long) savedInstanceState.getSerializable(NotesDbAdapter.KEY_ROWID);
    152 if (mRowId == null) {
    153     Bundle extras = getIntent().getExtras();
    154     mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
    155                             : null;
    156 }
    157 
    158 populateFields();
    159 
    160 confirmButton.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>
    173 private 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
    184 edit, then it calls <code>startManagingCursor()</code> from the <code>Activity</code> class, which
    185 is an Android convenience method provided to take care of the Cursor life-cycle. This will release
    186 and re-create resources as dictated by the Activity life-cycle, so we don't need to worry about
    187 doing that ourselves. After that, we just look up the title and body values from the Cursor
    188 and 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
    208 href="{@docRoot}guide/components/activities.html#Lifecycle">well-defined life
    209 cycle</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     &#64;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     &#64;Override
    258     protected void onPause() {
    259         super.onPause();
    260         saveState();
    261     }</pre>
    262     </li>
    263     <li><code>
    264       onResume()</code>:
    265       <pre>
    266     &#64;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>
    274 and <code>onPause()</code> to ensure that the data is saved.  This is because there is no
    275 guarantee that <code>onSaveInstanceState()</code> will be called and because when it <em>is</em>
    276 called, 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
    282 database.</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 &#64;Override
    314 protected 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>
    331 and 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>
    339 so 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>
    356 Run it! (use <em>Run As -&gt; Android Application</em> on the project right
    357 click 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>
    362 from
    363 the zip file to compare with your own.</p>
    364 <p>
    365 When you are ready, move on to the <a href="notepad-extra-credit.html">Tutorial
    366 Extra Credit</a> exercise, where you can use the Eclipse debugger to
    367 examine the life-cycle events as they happen.</p>
    368