From c88f13e0cbf16793009a7e8e62b6517c835ed6cb Mon Sep 17 00:00:00 2001
From: Tom O'Neill In this exercise, you will add a second Activity to your notepad application, to let the user
-create and edit notes. You will also allow the user to delete existing notes through a context menu.
+create and edit notes. You will also allow the user to delete existing notes through a context menu.
The new Activity assumes responsibility for creating new notes by
collecting user input and packing it into a return Bundle provided by the intent. This exercise
demonstrates:
onCreateContextMenu()
, onContextItemSelected()
,
+ (onCreateContextMenu()
, onContextItemSelected()
,
onListItemClick()
and onActivityResult()
)
which we will be filling in below.
registerForContextMenu()
and pass it our ListView. So, at the very end of
+ registerForContextMenu()
and pass it our ListView. So, at the very end of
the onCreate()
method add this line:
registerForContextMenu(getListView());
Because our Activity extends the ListActivity class, getListView()
will return us
@@ -91,7 +91,7 @@ public boolean onCreateContextMenu(Menu menu, View v
super.onCreateContextMenu(menu, v, menuInfo);
menu.add(0, DELETE_ID, 0, R.string.menu_delete);
}
-
The onCreateContextMenu()
callback some passes other information in addition to the Menu object,
+
The onCreateContextMenu()
callback passes some other information in addition to the Menu object,
such as the View that has been triggered for the menu and
an extra object that may contain additional information about the object selected. However, we don't care about
these here, because we only have one kind of object in the Activity that uses context menus. In the next
@@ -102,7 +102,7 @@ public boolean onCreateContextMenu(Menu menu, View v
Now that the we've registered our ListView for a context menu and defined our context menu item, we need
to handle the callback when it is selected. For this, we need to identify the list ID of the
- selected item, then delete it. So fill in the
+ selected item, then delete it. So fill in the
onContextItemSelected()
method like this:
public boolean onContextItemSelected(MenuItem item) { @@ -127,15 +127,15 @@ can now be deleted. margin-bottom:.5em;margin-top:1em;padding:0em;width:240px;">Starting Other Activities
-In this example our Intent uses a class name specifically. - As well as - starting intents in - classes we already know about, be they in our own application or another - application, we can also create Intents without knowing exactly which + As well as + starting intents in + classes we already know about, be they in our own application or another + application, we can also create Intents without knowing exactly which application will handle it.
-For example, we might want to open a page in a +
For example, we might want to open a page in a browser, and for this we still use an Intent. But instead of specifying a class to handle it, we use a predefined Intent constant, and a content URI that describes what we @@ -155,7 +155,7 @@ startActivityForResult(i, ACTIVITY_CREATE);
NoteEdit
. Since the Intent class will need to communicate with the Android
operating system to route requests, we also have to provide a Context (this
).
The startActivityForResult()
method fires the Intent in a way that causes a method
- in our Activity to be called when the new Activity is completed. The method in our Activity
+ in our Activity to be called when the new Activity is completed. The method in our Activity
that receives the callback is called
onActivityResult()
and we will implement it in a later step. The other way
to call an Activity is using startActivity()
but this is a "fire-and-forget" way
@@ -164,7 +164,7 @@ startActivityForResult(i, ACTIVITY_CREATE);
Don't worry about the fact that NoteEdit
doesn't exist yet,
we will fix that soon.
position
that the user selected. We use
this to get the data from the correct row, and bundle it up to send to
the NoteEdit
Activity.
- In our implementation of the callback, the method creates an +
In our implementation of the callback, the method creates an
Intent
to edit the note using
the NoteEdit
class. It then adds data into the extras Bundle of
the Intent, which will be passed to the called Activity. We use it
@@ -207,7 +207,7 @@ startActivityForResult(i, ACTIVITY_EDIT);
moveToPosition()
method.NoteEdit
class by passing startActivityForResult()
@@ -219,7 +219,7 @@ startActivityForResult(i, ACTIVITY_EDIT);
variable is much more efficient than accessing a field in the Dalvik VM, so by doing this
we make only one access to the field, and five accesses to the local variable, making the
routine much more efficient. It is recommended that you use this optimization when possible.
-
+
intent
— this is an Intent created by the Activity returning
results. It can be used to return data in the Intent "extras."
The combination of startActivityForResult()
and
onActivityResult()
can be thought of as an asynchronous RPC
(remote procedure call) and forms the recommended way for an Activity to invoke
@@ -277,7 +277,7 @@ case ACTIVITY_EDIT:
ACTIVITY_EDIT
activity results in this method.
fillData()
at the end ensures everything is up to date .
Take a look at the +
Take a look at the Hello Views for some example layouts and how to use them. The ApiDemos sample project is also a great resource from which to learn how to create different layouts.
@@ -335,7 +335,7 @@ case ACTIVITY_EDIT:layout_weight
specified, so it takes up the minimum space
required to render. If the layout_weight
of each of the two
text edit elements is set to 1, the remaining width in the parent layout will
- be split equally between them (because we claim they are equally important).
+ be split equally between them (because we claim they are equally important).
If the first one has a layout_weight
of 1
and the second has a layout_weight
of 2, then one third of the
remaining space will be given to the first, and two thirds to the
@@ -373,7 +373,7 @@ case ACTIVITY_EDIT:
onCreate(Bundle)
— and check the box next to it.
The method should now appear in your class.
Fill in the body of the onCreate()
method for NoteEdit
.
We can then unbundle the values that were passed in to the Activity
with the extras Bundle attached to the calling Intent. We'll use them to pre-populate
the title and body text edit views so that the user can edit them.
- Then we will grab and store the mRowId
so we can keep
+ Then we will grab and store the mRowId
so we can keep
track of what note the user is editing.
Note that mTitleText
and mBodyText
are member
+
Note that mTitleText
and mBodyText
are member
fields (you need to declare them at the top of the class definition).
Long mRowId
private field to store
the current mRowId
being edited (if any).
onCreate()
,
- add code to initialize the title
, body
and
+ onCreate()
,
+ add code to initialize the title
, body
and
mRowId
from the extras Bundle in
the Intent (if it is present):@@ -423,7 +423,7 @@ if (extras != null) { String title = extras.getString(NotesDbAdapter.KEY_TITLE); String body = extras.getString(NotesDbAdapter.KEY_BODY); mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID); - + if (title != null) { mTitleText.setText(title); } @@ -459,16 +459,16 @@ if (extras != null) { confirmButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { - + } - + });
Fill in the body of the onClick()
method of the OnClickListener
created in the last step.
This is the code that will be run when the user clicks on the
confirm button. We want this to grab the title and body text from the edit
text fields, and put them into the return Bundle so that they can be passed
@@ -483,7 +483,7 @@ confirmButton.setOnClickListener(new View.OnClickListener() {
constants defined in Notepadv2 as keys:
Bundle bundle = new Bundle(); - + bundle.putString(NotesDbAdapter.KEY_TITLE, mTitleText.getText().toString()); bundle.putString(NotesDbAdapter.KEY_BODY, mBodyText.getText().toString()); if (mRowId != null) { @@ -498,7 +498,7 @@ mIntent.putExtras(bundle); setResult(RESULT_OK, mIntent); finish();
setResult()
method is used to set the result
code and return Intent to be passed back to the
@@ -521,19 +521,19 @@ private Long mRowId;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_edit);
-
+
mTitleText = (EditText) findViewById(R.id.title);
mBodyText = (EditText) findViewById(R.id.body);
-
+
Button confirmButton = (Button) findViewById(R.id.confirm);
-
+
mRowId = null;
Bundle extras = getIntent().getExtras();
if (extras != null) {
String title = extras.getString(NotesDbAdapter.KEY_TITLE);
String body = extras.getString(NotesDbAdapter.KEY_BODY);
mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
-
+
if (title != null) {
mTitleText.setText(title);
}
@@ -541,12 +541,12 @@ protected void onCreate(Bundle savedInstanceState) {
mBodyText.setText(body);
}
}
-
+
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Bundle bundle = new Bundle();
-
+
bundle.putString(NotesDbAdapter.KEY_TITLE, mTitleText.getText().toString());
bundle.putString(NotesDbAdapter.KEY_BODY, mBodyText.getText().toString());
if (mRowId != null) {
@@ -562,7 +562,7 @@ protected void onCreate(Bundle savedInstanceState) {
}
The AndroidManifest.xml file is the way in which Android sees your +
The AndroidManifest.xml file is the way in which Android sees your application. This file defines the category of the application, where it shows up (or even if it shows up) in the launcher or settings, what activities, services, and content providers it defines, what intents it can receive, and more.
-For more information, see the reference document +
For more information, see the reference document The AndroidManifest.xml File
-Finally, the new Activity has to be defined in the manifest file:
Before the new Activity can be seen by Android, it needs its own @@ -597,10 +597,10 @@ protected void onCreate(Bundle savedInstanceState) {
If you see a dialog with radiobuttons at the top, select the top radio button: +
If you see a dialog with radiobuttons at the top, select the top radio button: "Create a new element at the top level, in Application".
.NoteEdit
into the Name*
field to the right. Press Return/Enter.If you prefer to edit this file directly, simply open the
AndroidManifest.xml
file and look at the source (use the
AndroidManifest.xml
tab in the eclipse editor to see the source code directly).
Then edit the file as follows:
- <activity android:name=".NoteEdit"></activity>
+ <activity android:name=".NoteEdit" />
This should be placed just below the line that reads:
</activity>
for the .Notepadv2
activity.
Now Run it!
-You should now be able to add real notes from -the menu, as well as delete an existing one. Notice that in order to delete, you must +
You should now be able to add real notes from +the menu, as well as delete an existing one. Notice that in order to delete, you must first use the directional controls on the device to highlight the note. -Furthermore, selecting a note title from the list should bring up the note -editor to let you edit it. Press confirm when finished to save the changes +Furthermore, selecting a note title from the list should bring up the note +editor to let you edit it. Press confirm when finished to save the changes back to the database.
You can see the solution to this exercise in Notepadv2Solution
+
You can see the solution to this exercise in Notepadv2Solution
from the zip file to compare with your own.
Now try editing a note, and then hitting the back button on the emulator instead of the confirm button (the back button is below the menu button). You diff --git a/docs/html/resources/tutorials/notepad/notepad-ex3.jd b/docs/html/resources/tutorials/notepad/notepad-ex3.jd index 8737280..2da113f 100644 --- a/docs/html/resources/tutorials/notepad/notepad-ex3.jd +++ b/docs/html/resources/tutorials/notepad/notepad-ex3.jd @@ -36,7 +36,7 @@ notes.
NoteEdit
that parses the title and body
- from the extras Bundle.
+ from the extras Bundle.
Instead, we are going to use the DBHelper
class
to access the notes from the database directly. All we need passed into the
NoteEdit Activity is a mRowId
(but only if we are editing, if creating we pass
@@ -57,7 +57,7 @@ if (body != null) {
}
Create a class field for a NotesDbAdapter
at the top of the NoteEdit class:
mDbHelper = new NotesDbAdapter(this);- +
mDbHelper.open();
In NoteEdit
, we need to check the savedInstanceState for the
-mRowId
, in case the note
+mRowId
, in case the note
editing contains a saved state in the Bundle, which we should recover (this would happen
if our Activity lost focus and then restarted).
- mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID) - : null; + mRowId = (savedInstanceState == null) ? null : + (Long) savedInstanceState.getSerializable(NotesDbAdapter.KEY_ROWID); if (mRowId == null) { - Bundle extras = getIntent().getExtras(); - mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID) + Bundle extras = getIntent().getExtras(); + mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID) : null; }@@ -100,10 +100,15 @@ if (body != null) { Note the null check for
savedInstanceState
, and we still need to load up
mRowId
from the extras
Bundle if it is not
provided by the savedInstanceState
. This is a ternary operator shorthand
- to safely either use the value or null if it is not present.
+ to safely either use the value or null if it is not present.
+
+ Bundle.getSerializable()
instead of
+ Bundle.getLong()
. The latter encoding returns a long
primitive and
+ so can not be used to represent the case when mRowId
is null
.
Next, we need to populate the fields based on the mRowId
if we
@@ -126,38 +131,38 @@ public void onClick(View view) {
}
We will take care of storing the updates or new notes in the database ourselves, using the life-cycle methods.
- +The whole onCreate()
method should now look like this:
super.onCreate(savedInstanceState); - + mDbHelper = new NotesDbAdapter(this); mDbHelper.open(); - + setContentView(R.layout.note_edit); - + mTitleText = (EditText) findViewById(R.id.title); mBodyText = (EditText) findViewById(R.id.body); - + Button confirmButton = (Button) findViewById(R.id.confirm); - -mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID) - : null; + +mRowId = (savedInstanceState == null) ? null : + (Long) savedInstanceState.getSerializable(NotesDbAdapter.KEY_ROWID); if (mRowId == null) { Bundle extras = getIntent().getExtras(); - mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID) + mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID) : null; } - + populateFields(); - + confirmButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { setResult(RESULT_OK); finish(); } - + });
If you are used to always having control in your applications, you might not understand why all this life-cycle work is necessary. The reason - is that in Android, you are not in control of your Activity, the + is that in Android, you are not in control of your Activity, the operating system is!
-As we have already seen, the Android model is based around activities calling each other. When one Activity calls another, the current Activity is paused at the very least, and may be killed altogether if the @@ -209,9 +214,9 @@ and populate the View elements with them.
out while the call Activity takes over. -Still in the NoteEdit
class, we now override the methods
- onSaveInstanceState()
, onPause()
and
- onResume()
. These are our life-cycle methods
+
Still in the NoteEdit
class, we now override the methods
+ onSaveInstanceState()
, onPause()
and
+ onResume()
. These are our life-cycle methods
(along with onCreate()
which we already have).
onSaveInstanceState()
is called by Android if the
@@ -241,8 +246,10 @@ and populate the View elements with them.
We'll define saveState()
next.
onPause()
:
@@ -252,7 +259,6 @@ and populate the View elements with them.
super.onPause();
saveState();
}
- We'll define saveState()
next.
onResume()
:
@@ -264,6 +270,10 @@ and populate the View elements with them.
}
Note that saveState()
must be called in both onSaveInstanceState()
+and onPause()
to ensure that the data is saved. This is because there is no
+guarantee that onSaveInstanceState()
will be called and because when it is
+called, it is called before onPause()
.
@Override -protected void onActivityResult(int requestCode, int resultCode, - Intent intent) { +protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); fillData(); }
Because the other class now does the work, all this has to do is refresh the data.
- +Also remove the lines which set the title and body from the
- onListItemClick()
method (again they are no longer needed,
+ onListItemClick()
method (again they are no longer needed,
only the mRowId
is):
Cursor c = mNotesCursor; @@ -344,13 +353,13 @@ so that all that should be left in that method is: other occurrences ofmNotesCursor
in yourfillData()
method.-Run it! (use Run As -> Android Application on the project right +Run it! (use Run As -> Android Application on the project right click menu again)
Solution and Next Steps
You can see the solution to this exercise in
Notepadv3Solution
-from +from the zip file to compare with your own.When you are ready, move on to the Tutorial -- cgit v1.1