-
Notifications
You must be signed in to change notification settings - Fork 717
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,9 +15,11 @@ | |
*/ | ||
package com.example.android.pets; | ||
|
||
import android.app.AlertDialog; | ||
import android.app.LoaderManager; | ||
import android.content.ContentValues; | ||
import android.content.CursorLoader; | ||
import android.content.DialogInterface; | ||
import android.content.Intent; | ||
import android.content.Loader; | ||
import android.database.Cursor; | ||
|
@@ -28,6 +30,7 @@ | |
import android.text.TextUtils; | ||
import android.view.Menu; | ||
import android.view.MenuItem; | ||
import android.view.MotionEvent; | ||
import android.view.View; | ||
import android.widget.AdapterView; | ||
import android.widget.ArrayAdapter; | ||
|
@@ -68,6 +71,21 @@ public class EditorActivity extends AppCompatActivity implements | |
*/ | ||
private int mGender = PetEntry.GENDER_UNKNOWN; | ||
|
||
/** Boolean flag that keeps track of whether the pet has been edited (true) or not (false) */ | ||
private boolean mPetHasChanged = false; | ||
|
||
/** | ||
* OnTouchListener that listens for any user touches on a View, implying that they are modifying | ||
* the view, and we change the mPetHasChanged boolean to true. | ||
*/ | ||
private View.OnTouchListener mTouchListener = new View.OnTouchListener() { | ||
@Override | ||
public boolean onTouch(View view, MotionEvent motionEvent) { | ||
mPetHasChanged = true; | ||
return false; | ||
} | ||
}; | ||
|
||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
megandelrosario
|
||
@Override | ||
protected void onCreate(Bundle savedInstanceState) { | ||
super.onCreate(savedInstanceState); | ||
|
@@ -98,6 +116,14 @@ protected void onCreate(Bundle savedInstanceState) { | |
mWeightEditText = (EditText) findViewById(R.id.edit_pet_weight); | ||
mGenderSpinner = (Spinner) findViewById(R.id.spinner_gender); | ||
|
||
// Setup OnTouchListeners on all the input fields, so we can determine if the user | ||
// has touched or modified them. This will let us know if there are unsaved changes | ||
// or not, if the user tries to leave the editor without saving. | ||
mNameEditText.setOnTouchListener(mTouchListener); | ||
This comment has been minimized.
Sorry, something went wrong.
darwinyip
|
||
mBreedEditText.setOnTouchListener(mTouchListener); | ||
mWeightEditText.setOnTouchListener(mTouchListener); | ||
mGenderSpinner.setOnTouchListener(mTouchListener); | ||
|
||
setupSpinner(); | ||
} | ||
|
||
|
@@ -235,13 +261,58 @@ public boolean onOptionsItemSelected(MenuItem item) { | |
return true; | ||
// Respond to a click on the "Up" arrow button in the app bar | ||
case android.R.id.home: | ||
// Navigate back to parent activity (CatalogActivity) | ||
NavUtils.navigateUpFromSameTask(this); | ||
// If the pet hasn't changed, continue with navigating up to parent activity | ||
// which is the {@link CatalogActivity}. | ||
if (!mPetHasChanged) { | ||
NavUtils.navigateUpFromSameTask(EditorActivity.this); | ||
return true; | ||
} | ||
|
||
// Otherwise if there are unsaved changes, setup a dialog to warn the user. | ||
// Create a click listener to handle the user confirming that | ||
// changes should be discarded. | ||
DialogInterface.OnClickListener discardButtonClickListener = | ||
new DialogInterface.OnClickListener() { | ||
@Override | ||
public void onClick(DialogInterface dialogInterface, int i) { | ||
// User clicked "Discard" button, navigate to parent activity. | ||
NavUtils.navigateUpFromSameTask(EditorActivity.this); | ||
} | ||
}; | ||
|
||
// Show a dialog that notifies the user they have unsaved changes | ||
showUnsavedChangesDialog(discardButtonClickListener); | ||
return true; | ||
} | ||
return super.onOptionsItemSelected(item); | ||
} | ||
|
||
/** | ||
* This method is called when the back button is pressed. | ||
*/ | ||
@Override | ||
public void onBackPressed() { | ||
// If the pet hasn't changed, continue with handling back button press | ||
if (!mPetHasChanged) { | ||
super.onBackPressed(); | ||
return; | ||
} | ||
|
||
// Otherwise if there are unsaved changes, setup a dialog to warn the user. | ||
// Create a click listener to handle the user confirming that changes should be discarded. | ||
DialogInterface.OnClickListener discardButtonClickListener = | ||
new DialogInterface.OnClickListener() { | ||
@Override | ||
public void onClick(DialogInterface dialogInterface, int i) { | ||
// User clicked "Discard" button, close the current activity. | ||
finish(); | ||
} | ||
}; | ||
|
||
// Show dialog that there are unsaved changes | ||
showUnsavedChangesDialog(discardButtonClickListener); | ||
} | ||
|
||
@Override | ||
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { | ||
// Since the editor shows all pet attributes, define a projection that contains | ||
|
@@ -314,4 +385,33 @@ public void onLoaderReset(Loader<Cursor> loader) { | |
mWeightEditText.setText(""); | ||
mGenderSpinner.setSelection(0); // Select "Unknown" gender | ||
} | ||
|
||
/** | ||
* Show a dialog that warns the user there are unsaved changes that will be lost | ||
* if they continue leaving the editor. | ||
* | ||
* @param discardButtonClickListener is the click listener for what to do when | ||
* the user confirms they want to discard their changes | ||
*/ | ||
private void showUnsavedChangesDialog( | ||
DialogInterface.OnClickListener discardButtonClickListener) { | ||
// Create an AlertDialog.Builder and set the message, and click listeners | ||
// for the postivie and negative buttons on the dialog. | ||
AlertDialog.Builder builder = new AlertDialog.Builder(this); | ||
builder.setMessage(R.string.unsaved_changes_dialog_msg); | ||
builder.setPositiveButton(R.string.discard, discardButtonClickListener); | ||
builder.setNegativeButton(R.string.keep_editing, new DialogInterface.OnClickListener() { | ||
public void onClick(DialogInterface dialog, int id) { | ||
// User clicked the "Keep editing" button, so dismiss the dialog | ||
// and continue editing the pet. | ||
if (dialog != null) { | ||
dialog.dismiss(); | ||
} | ||
} | ||
This comment has been minimized.
Sorry, something went wrong.
n-abdelmaksoud
|
||
}); | ||
|
||
// Create and show the AlertDialog | ||
AlertDialog alertDialog = builder.create(); | ||
alertDialog.show(); | ||
} | ||
} |
28 comments
on commit bea7d90
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oddly enough, this code does nothing when I return to the catalog activity without saving. It happily closes the editor activity (without saving).
I am working with an emulator (Api22) β might there be something that the emulator ignores or is not supported by it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@atschpe You're probably missing the final return true;
in the android.R.id.home switch case in onOptionsItemSelected(...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we just edit the name and quit(back or up) without saving, It won't show the alertDialog.
OnTouchListener is not appropriate for listening to the changes. Since we didn't touch any other Views, the variable mPetHasChanged won't be True, but we still can edit the name.
Any suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@xingped I've doublechecked and return true
; is set on each switch case. So sadly that is not what is causing it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To fix the mistake change "int id" => "int which" in:
builder.setNegativeButton(R.string.keep_editing, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (dialog != null){ dialog.dismiss(); } }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For myself, I found it simpler to set up showUnsavedChangesDialog()
with no argument and then set the .setPositiveButton()
as below:
private void showUnsavedChangesDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.unsaved_changes_dialog_msg)
.setPositiveButton(R.string.discard, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
This way, when I'm calling showUnsavedChangesDialog()
, I simply call the alert dialog w/o having to define the onClick()
method in both onBackPressed()
and onOptionsItemSelected()
.
case android.R.id.home:
if (!mPetHasChanged) {
NavUtils.navigateUpFromSameTask(this);
return true;
}
showUnsavedChangesDialog();
return true;
Seems to behave correctly. Am I missing something by not implementing code as written by Udacity?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, not sure why we have to do if (dialog != null)
here
.setNegativeButton(R.string.keep_editing, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
// User clicked the "Keep editing" button, so dismiss the dialog
// and continue editing the pet.
if (dialog != null) {
dialog.dismiss();
}
}
Isn't the act of clicking on the "Keep editing" button means that we're calling the .setNegativeButton()
? So we can just do:
.setNegativeButton(R.string.keep_editing, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// User clicked the "Keep editing" button, so dismiss the dialog
// and continue editing the pet.
dialog.dismiss();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding navigation, there's a difference between calling finish()
and NavUtils.navigateUpFromSameTask(EditorActivity.this)
See the training documentation https://developer.android.com/training/design-navigation/index.html
You don't really see it in such a small app with only two activities, but when you start designing more complicated navigation structures, or make it possible to get to a certain activity from outside the app, the behavior of these two methods will be different. Either way is fine for this app.
I don't know why it's necessary to check if (dialog != null)
When I get a chance I'll review the video explaining that one to see if they give any hints about why they added that check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@linucksrox
For if (dialog != null)
, one explanation I was told was that it was good practice to check if the dialog window was present or not before dismissing it.
I'm not entirely sure I understand that explanation. Maybe in this case, the dialog is pretty cut and dry but in other cases, there may be an instance where a dialog is dismissed and you're responding to it via another dialog or activity...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @my-jabin ,
I think the logic is if the user click/touch on a View then the onTouchListener will work.
Since when the first time we go into EditorActivity the cursor is directly on mNameEditText, it doesn't count as if we click/touch on mNameEditText. So when we pressed on back button or Up button then the Dialog won't pop up.
Try to click/touch on the mNameEditText first then edit the pet name. it'll pop up the Dialog when you pressed on back / up button.
CMIIW
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't overthink it, swap
if (!mPetHasChanged)
to
if (!mPetHasChanged && TextUtils.isEmpty(mNameEditText.getText().toString()))
in both cases.
Will work even if user doesn't trigger onTouchListener by staying in Name edit box. The only problem is when user actually uses Tab on attached physical keyboard to move between fields... but if you want to cover such cases, just add remaining fields in same manner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To fix it add in root LinearLayout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true" >
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After edit or touch any field and then rotate the device, the dialog box doesn't pop up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no problem
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("petHasChanged", mPetHasChanged);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mPetHasChanged = savedInstanceState.getBoolean("petHasChanged");
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should add a line of code before dismissing the dialog:
mPetsHasChanges=false;
dialog.dismiss();
to reset its value so if the user presses back button again but this time without changes, the EditorActivity finishes without displaying the dialog again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nerrydanna You are smart bunny π―
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nerrydanna no you cannot do that because if you click back button before clickiing at the save button then your data will not be saved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if User Touch any Edit Field, so 'OnTouchListener' get Triggered and 'mPetHasChanged' value will change to 'true' and then if user Rotate the screen, so Editor activity will again created with Default value of 'mPetHasChanged' value to 'false', so it's not good.
SOLUTION: In 'EditorActivity.java'
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("mPetHasChanged", mPetHasChanged);
}
Now in onCreate():
if(savedInstanceState != null){
mPetHasChanged = savedInstanceState.getBoolean("mPetHasChanged");
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why they are spitting so much code and changes without explanation i don't understand...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a problem in this one ,when i press back button it did not display the dialog box and there is no back button showing at the top of the activity.Help!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found a test case where the dialog box was not appearing when the user added details for a new pet but then clicking the back button before saving the details.
Steps to Reproduce:
- run the code on an emulator
- click on the FAB to add a new Pet (using your computer keyboard mouse)
- the add Pet screen opens with Focus on the Name field (* Note - the soft keyboard does not appear in the emulator)
- start typing - whatever you type should appear in the name field
- click the back button - Here the dialog box is not appearing
I believe this is because no touch event has been detected because you never actually clicked anywhere within the emulator with your mouse.
- if, at step 3 above, you use your mouse and click into the Name field, two things happen, the touch is detected and the soft keyboard appears.
How I resolved it:
To resolve this test case I added an onKeylistener like so to the EditorActivity.java, just below the View.OnTouchListener.
private View.OnKeyListener mKeyListener = new View.OnKeyListener(){ @Override public boolean onKey(View v, int keyCode, KeyEvent event){ mPetHasChanged = true; return false; } };
Then set the onkeylistners in line with the setOnTouchListeners:
mNameEditText.setOnTouchListener(mTouchListener); mNameEditText.setOnKeyListener(mKeyListener); mBreedEditText.setOnTouchListener(mTouchListener); mBreedEditText.setOnKeyListener(mKeyListener); mWeightEditText.setOnTouchListener(mTouchListener); mWeightEditText.setOnKeyListener(mKeyListener); mGenderSpinner.setOnTouchListener(mTouchListener);
While this approach seems to work ok, Is this a good way to deal with the issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a bug, but it would be really nice to set the boolean variable back to "FALSE" when we successfully update the pet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After edit or touch any field and then rotate the device, the dialog box doesn't pop up.
Try this when you're setting the AlertDialog:
builder.setCancelable(false);
This will prevent from clicking outside to close the dialog window.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a bug, but it would be really nice to set the boolean variable back to "FALSE" when we successfully update the pet.
No need. When you successfully update the pet means that you have quit the Editor Activity, and next time you enter Editor Activity everything will be reset.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Home Button going from Editor activity to Catalog activity, we are writing:
NavUtils.navigateUpFromSameTask(EditorActivity.this);
Why not we are writing
Intent intent = new Intent(EditorActivity.this, CatalogActivity.class);
startActivity(intent);
What's the difference
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SayantanBanerjee16 "Up" button which appears in the top left corner brings you to the upper level Activity (by specifying the "parent_activity" attribute in AndroidManifest file), whereas the universal "back" key brings you to the previous screen which can be the screen of another app.
Intent is always creating a new instance of the target Activity class, whereas "up" can resume the state of parent Activity which follows the Activity life cycle rule. Further discusstion: https://stackoverflow.com/questions/40629176/is-there-any-difference-between-using-the-up-button-and-using-intent-to-go-up-on.
By the way, "up" button will call "onCreate()", "onStart()" and "onResume()", which give you the chance to run some code on them (latter two). If you don't want the "onCretae()" get called you can set "launchMode" to "singleTop" in Activity attribute in AndroidManifest file.
Good luck.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The upper left back arrow, does not wait me to choose, Edit or Discard, it closes the activity it self.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (cursor.moveToFirst()) {
//Toast.makeText(this, cursorPositoin,Toast.LENGTH_LONG).show();
// Find the columns of pet attributes that we're interested in
int nameColumnIndex = cursor.getColumnIndex(PetContract.PetEntry.COLUMN_NAME);
int breedColumnIndex = cursor.getColumnIndex(PetContract.PetEntry.COLUMN_BREED);
int genderColumnIndex = cursor.getColumnIndex(PetContract.PetEntry.COLUMN_GENDER);
int weightColumnIndex = cursor.getColumnIndex(PetContract.PetEntry.COLUMN_WEIGHT);
...etc
this method always return the first table record instead of readable the record according to pet id. Any idea how to fix it plz ?
why " return false" not "true"???