diff options
Diffstat (limited to 'samples/NotePad/src/com/example/android/notepad/NoteEditor.java')
-rw-r--r-- | samples/NotePad/src/com/example/android/notepad/NoteEditor.java | 297 |
1 files changed, 136 insertions, 161 deletions
diff --git a/samples/NotePad/src/com/example/android/notepad/NoteEditor.java b/samples/NotePad/src/com/example/android/notepad/NoteEditor.java index 59d6f1290..b8b070f20 100644 --- a/samples/NotePad/src/com/example/android/notepad/NoteEditor.java +++ b/samples/NotePad/src/com/example/android/notepad/NoteEditor.java @@ -17,13 +17,16 @@ package com.example.android.notepad; import android.app.Activity; +import android.app.LoaderManager; import android.content.ClipData; import android.content.ClipboardManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; +import android.content.CursorLoader; import android.content.Intent; +import android.content.Loader; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Canvas; @@ -37,19 +40,15 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.widget.EditText; +import com.example.android.notepad.NotePad.Notes; /** * This Activity handles "editing" a note, where editing is responding to * {@link Intent#ACTION_VIEW} (request to view data), edit a note * {@link Intent#ACTION_EDIT}, create a note {@link Intent#ACTION_INSERT}, or * create a new note from the current contents of the clipboard {@link Intent#ACTION_PASTE}. - * - * NOTE: Notice that the provider operations in this Activity are taking place on the UI thread. - * This is not a good practice. It is only done here to make the code more readable. A real - * application should use the {@link android.content.AsyncQueryHandler} - * or {@link android.os.AsyncTask} object to perform operations asynchronously on a separate thread. */ -public class NoteEditor extends Activity { +public class NoteEditor extends Activity implements LoaderManager.LoaderCallbacks<Cursor> { // For logging and debugging purposes private static final String TAG = "NoteEditor"; @@ -71,10 +70,11 @@ public class NoteEditor extends Activity { private static final int STATE_EDIT = 0; private static final int STATE_INSERT = 1; + private static final int LOADER_ID = 1; + // Global mutable variables private int mState; private Uri mUri; - private Cursor mCursor; private EditText mText; private String mOriginalContent; @@ -139,6 +139,11 @@ public class NoteEditor extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // Recovering the instance state from a previously destroyed Activity instance + if (savedInstanceState != null) { + mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT); + } + /* * Creates an Intent to use when the Activity object's result is sent back to the * caller. @@ -166,6 +171,8 @@ public class NoteEditor extends Activity { // Sets the Activity state to INSERT, gets the general note URI, and inserts an // empty record in the provider mState = STATE_INSERT; + setTitle(getText(R.string.title_create)); + mUri = getContentResolver().insert(intent.getData(), null); /* @@ -197,24 +204,10 @@ public class NoteEditor extends Activity { return; } - /* - * Using the URI passed in with the triggering Intent, gets the note or notes in - * the provider. - * Note: This is being done on the UI thread. It will block the thread until the query - * completes. In a sample app, going against a simple provider based on a local database, - * the block will be momentary, but in a real app you should use - * android.content.AsyncQueryHandler or android.os.AsyncTask. - */ - mCursor = managedQuery( - mUri, // The URI that gets multiple notes from the provider. - PROJECTION, // A projection that returns the note ID and note content for each note. - null, // No "where" clause selection criteria. - null, // No "where" clause selection values. - null // Use the default sort order (modification date, descending) - ); + // Initialize the LoaderManager and start the query + getLoaderManager().initLoader(LOADER_ID, null, this); // For a paste, initializes the data from clipboard. - // (Must be done after mCursor is initialized.) if (Intent.ACTION_PASTE.equals(action)) { // Does the paste performPaste(); @@ -227,87 +220,12 @@ public class NoteEditor extends Activity { // Gets a handle to the EditText in the the layout. mText = (EditText) findViewById(R.id.note); - - /* - * If this Activity had stopped previously, its state was written the ORIGINAL_CONTENT - * location in the saved Instance state. This gets the state. - */ - if (savedInstanceState != null) { - mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT); - } } - /** - * This method is called when the Activity is about to come to the foreground. This happens - * when the Activity comes to the top of the task stack, OR when it is first starting. - * - * Moves to the first note in the list, sets an appropriate title for the action chosen by - * the user, puts the note contents into the TextView, and saves the original text as a - * backup. - */ - @Override - protected void onResume() { - super.onResume(); - - /* - * mCursor is initialized, since onCreate() always precedes onResume for any running - * process. This tests that it's not null, since it should always contain data. - */ - if (mCursor != null) { - // Requery in case something changed while paused (such as the title) - mCursor.requery(); - - /* Moves to the first record. Always call moveToFirst() before accessing data in - * a Cursor for the first time. The semantics of using a Cursor are that when it is - * created, its internal index is pointing to a "place" immediately before the first - * record. - */ - mCursor.moveToFirst(); - - // Modifies the window title for the Activity according to the current Activity state. - if (mState == STATE_EDIT) { - // Set the title of the Activity to include the note title - int colTitleIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE); - String title = mCursor.getString(colTitleIndex); - Resources res = getResources(); - String text = String.format(res.getString(R.string.title_edit), title); - setTitle(text); - // Sets the title to "create" for inserts - } else if (mState == STATE_INSERT) { - setTitle(getText(R.string.title_create)); - } - - /* - * onResume() may have been called after the Activity lost focus (was paused). - * The user was either editing or creating a note when the Activity paused. - * The Activity should re-display the text that had been retrieved previously, but - * it should not move the cursor. This helps the user to continue editing or entering. - */ - - // Gets the note text from the Cursor and puts it in the TextView, but doesn't change - // the text cursor's position. - int colNoteIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE); - String note = mCursor.getString(colNoteIndex); - mText.setTextKeepState(note); - - // Stores the original note text, to allow the user to revert changes. - if (mOriginalContent == null) { - mOriginalContent = note; - } - - /* - * Something is wrong. The Cursor should always contain data. Report an error in the - * note. - */ - } else { - setTitle(getText(R.string.error_title)); - mText.setText(getText(R.string.error_message)); - } - } /** - * This method is called when an Activity loses focus during its normal operation, and is then - * later on killed. The Activity has a chance to save its state so that the system can restore + * This method is called when an Activity loses focus during its normal operation. + * The Activity has a chance to save its state so that the system can restore * it. * * Notice that this method isn't a normal part of the Activity lifecycle. It won't be called @@ -316,37 +234,52 @@ public class NoteEditor extends Activity { @Override protected void onSaveInstanceState(Bundle outState) { // Save away the original text, so we still have it if the activity - // needs to be killed while paused. + // needs to be re-created. outState.putString(ORIGINAL_CONTENT, mOriginalContent); + // Call the superclass to save the any view hierarchy state + super.onSaveInstanceState(outState); } /** * This method is called when the Activity loses focus. * - * For Activity objects that edit information, onPause() may be the one place where changes are - * saved. The Android application model is predicated on the idea that "save" and "exit" aren't - * required actions. When users navigate away from an Activity, they shouldn't have to go back - * to it to complete their work. The act of going away should save everything and leave the - * Activity in a state where Android can destroy it if necessary. - * - * If the user hasn't done anything, then this deletes or clears out the note, otherwise it - * writes the user's work to the provider. + * While there is no need to override this method in this app, it is shown here to highlight + * that we are not saving any state in onPause, but have moved app state saving to onStop + * callback. + * In earlier versions of this app and popular literature it had been shown that onPause is good + * place to persist any unsaved work, however, this is not really a good practice because of how + * application and process lifecycle behave. + * As a general guideline apps should have a way of saving their business logic that does not + * solely rely on Activity (or other component) lifecyle state transitions. + * As a backstop you should save any app state, not saved during lifetime of the Activity, in + * onStop(). + * For a more detailed explanation of this recommendation please read + * <a href = "https://developer.android.com/guide/topics/processes/process-lifecycle.html"> + * Processes and Application Life Cycle </a>. + * <a href="https://developer.android.com/training/basics/activity-lifecycle/pausing.html"> + * Pausing and Resuming an Activity </a>. */ @Override protected void onPause() { super.onPause(); + } - /* - * Tests to see that the query operation didn't fail (see onCreate()). The Cursor object - * will exist, even if no records were returned, unless the query failed because of some - * exception or error. - * - */ - if (mCursor != null) { + /** + * This method is called when the Activity becomes invisible. + * + * For Activity objects that edit information, onStop() may be the one place where changes maybe + * saved. + * + * If the user hasn't done anything, then this deletes or clears out the note, otherwise it + * writes the user's work to the provider. + */ + @Override + protected void onStop() { + super.onStop(); - // Get the current note text. - String text = mText.getText().toString(); - int length = text.length(); + // Get the current note text. + String text = mText.getText().toString(); + int length = text.length(); /* * If the Activity is in the midst of finishing and there is no text in the current @@ -354,23 +287,22 @@ public class NoteEditor extends Activity { * even if the note was being edited, the assumption being that the user wanted to * "clear out" (delete) the note. */ - if (isFinishing() && (length == 0)) { - setResult(RESULT_CANCELED); - deleteNote(); + if (isFinishing() && (length == 0)) { + setResult(RESULT_CANCELED); + deleteNote(); /* - * Writes the edits to the provider. The note has been edited if an existing note was - * retrieved into the editor *or* if a new note was inserted. In the latter case, - * onCreate() inserted a new empty note into the provider, and it is this new note - * that is being edited. + * Writes the edits to the provider. The note has been edited if an existing note + * was retrieved into the editor *or* if a new note was inserted. + * In the latter case, onCreate() inserted a new empty note into the provider, + * and it is this new note that is being edited. */ - } else if (mState == STATE_EDIT) { - // Creates a map to contain the new values for the columns - updateNote(text, null); - } else if (mState == STATE_INSERT) { - updateNote(text, text); - mState = STATE_EDIT; - } + } else if (mState == STATE_EDIT) { + // Creates a map to contain the new values for the columns + updateNote(text, null); + } else if (mState == STATE_INSERT) { + updateNote(text, text); + mState = STATE_EDIT; } } @@ -409,8 +341,16 @@ public class NoteEditor extends Activity { @Override public boolean onPrepareOptionsMenu(Menu menu) { // Check if note has changed and enable/disable the revert option - int colNoteIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE); - String savedNote = mCursor.getString(colNoteIndex); + Cursor cursor = getContentResolver().query( + mUri, // The URI for the note that is to be retrieved. + PROJECTION, // The columns to retrieve + null, // No selection criteria are used, so no where columns are needed. + null, // No where columns are used, so no where values are needed. + null // No sort order is needed. + ); + cursor.moveToFirst(); + int colNoteIndex = cursor.getColumnIndex(Notes.COLUMN_NAME_NOTE); + String savedNote = cursor.getString(colNoteIndex); String currentNote = mText.getText().toString(); if (savedNote.equals(currentNote)) { menu.findItem(R.id.menu_revert).setVisible(false); @@ -493,8 +433,8 @@ public class NoteEditor extends Activity { // (moveToFirst() returns true), then this gets the note data from it. if (orig != null) { if (orig.moveToFirst()) { - int colNoteIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE); - int colTitleIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE); + int colNoteIndex = orig.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE); + int colTitleIndex = orig.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE); text = orig.getString(colNoteIndex); title = orig.getString(colTitleIndex); } @@ -571,13 +511,11 @@ public class NoteEditor extends Activity { * android.content.AsyncQueryHandler or android.os.AsyncTask. */ getContentResolver().update( - mUri, // The URI for the record to update. - values, // The map of column names and new values to apply to them. - null, // No selection criteria are used, so no where columns are necessary. - null // No where columns are used, so no where arguments are necessary. - ); - - + mUri, // The URI for the record to update. + values, // The map of column names and new values to apply to them. + null, // No selection criteria are used, so no where columns are necessary. + null // No where columns are used, so no where arguments are necessary. + ); } /** @@ -585,19 +523,17 @@ public class NoteEditor extends Activity { * newly created, or reverts to the original text of the note i */ private final void cancelNote() { - if (mCursor != null) { - if (mState == STATE_EDIT) { - // Put the original note text back into the database - mCursor.close(); - mCursor = null; - ContentValues values = new ContentValues(); - values.put(NotePad.Notes.COLUMN_NAME_NOTE, mOriginalContent); - getContentResolver().update(mUri, values, null, null); - } else if (mState == STATE_INSERT) { - // We inserted an empty note, make sure to delete it - deleteNote(); - } + + if (mState == STATE_EDIT) { + // Put the original note text back into the database + ContentValues values = new ContentValues(); + values.put(NotePad.Notes.COLUMN_NAME_NOTE, mOriginalContent); + getContentResolver().update(mUri, values, null, null); + } else if (mState == STATE_INSERT) { + // We inserted an empty note, make sure to delete it + deleteNote(); } + setResult(RESULT_CANCELED); finish(); } @@ -606,11 +542,50 @@ public class NoteEditor extends Activity { * Take care of deleting a note. Simply deletes the entry. */ private final void deleteNote() { - if (mCursor != null) { - mCursor.close(); - mCursor = null; - getContentResolver().delete(mUri, null, null); - mText.setText(""); + getContentResolver().delete(mUri, null, null); + mText.setText(""); + } + + // LoaderManager callbacks + @Override + public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { + return new CursorLoader( + this, + mUri, // The URI for the note that is to be retrieved. + PROJECTION, // The columns to retrieve + null, // No selection criteria are used, so no where columns are needed. + null, // No where columns are used, so no where values are needed. + null // No sort order is needed. + ); + } + + @Override + public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) { + + // Modifies the window title for the Activity according to the current Activity state. + if (cursor != null && cursor.moveToFirst() && mState == STATE_EDIT) { + // Set the title of the Activity to include the note title + int colTitleIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE); + int colNoteIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE); + + // Gets the title and sets it + String title = cursor.getString(colTitleIndex); + Resources res = getResources(); + String text = String.format(res.getString(R.string.title_edit), title); + setTitle(text); + + // Gets the note text from the Cursor and puts it in the TextView, but doesn't change + // the text cursor's position. + + String note = cursor.getString(colNoteIndex); + mText.setTextKeepState(note); + // Stores the original note text, to allow the user to revert changes. + if (mOriginalContent == null) { + mOriginalContent = note; + } } } + + @Override + public void onLoaderReset(Loader<Cursor> cursorLoader) {} } |