activity_catalog.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CatalogActivity">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- Empty view for the list -->
<RelativeLayout
android:id="@+id/empty_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<ImageView
android:id="@+id/empty_shelter_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:src="@drawable/ic_empty_shelter"/>
<TextView
android:id="@+id/empty_title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/empty_shelter_image"
android:layout_centerHorizontal="true"
android:fontFamily="sans-serif-medium"
android:paddingTop="16dp"
android:text="@string/empty_view_title_text"
android:textAppearance="?android:textAppearanceMedium"/>
<TextView
android:id="@+id/empty_subtitle_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/empty_title_text"
android:layout_centerHorizontal="true"
android:fontFamily="sans-serif"
android:paddingTop="8dp"
android:text="@string/empty_view_subtitle_text"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="#A2AAB0"/>
</RelativeLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="@dimen/fab_margin"
android:src="@drawable/ic_add_pet"/>
</RelativeLayout>
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Layout for a single list item in the list of pets -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/activity_margin">
<TextView
android:id="@+id/pet_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:textAppearance="?android:textAppearanceMedium"
android:textColor="#2B3D4D" />
<TextView
android:id="@+id/pet_breed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="#AEB6BD" />
</LinearLayout>
CatalogActivity.java
package com.example.android.pets;
import android.app.LoaderManager;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import com.example.android.pets.data.PetContract;
import com.example.android.pets.data.PetContract.PetEntry;
import com.example.android.pets.data.PetDbHelper;
import com.example.android.pets.data.PetProvider;
/**
* Displays list of pets that were entered and stored in the app.
*/
public class CatalogActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks {
private static final int PET_LOADER = 0;
PetCursorAdapter petCursorAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_catalog);
// Setup FAB to open EditorActivity
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(CatalogActivity.this, EditorActivity.class);
startActivity(intent);
}
});
// Find ListView to populate
ListView lvItems = (ListView) findViewById(R.id.list_view);
// Find and set empty view on the ListView, so that it only shows when the list has 0 items.
View emptyView = findViewById(R.id.empty_view);
lvItems.setEmptyView(emptyView);
// Setup cursor adapter using cursor from last step
petCursorAdapter = new PetCursorAdapter(this, null);
// Attach cursor adapter to the ListView
lvItems.setAdapter(petCursorAdapter);
getLoaderManager().initLoader(PET_LOADER,null, this);
lvItems.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView adapterView, View view, int position, long id) {
Intent editAct = new Intent(CatalogActivity.this,EditorActivity.class);
Uri currentPetUri = ContentUris.withAppendedId(PetEntry.CONTENT_URI,id);
editAct.setData(currentPetUri);
startActivity(editAct);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu options from the res/menu/menu_catalog.xml file.
// This adds menu items to the app bar.
getMenuInflater().inflate(R.menu.menu_catalog, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// User clicked on a menu option in the app bar overflow menu
switch (item.getItemId()) {
// Respond to a click on the "Insert dummy data" menu option
case R.id.action_insert_dummy_data:
insertPet();
return true;
// Respond to a click on the "Delete all entries" menu option
case R.id.action_delete_all_entries:
deleteAllPets();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Helper method to insert hardcoded pet data into the database. For debugging purposes only.
*/
private void insertPet() {
// Create a ContentValues object where column names are the keys,
// and Toto's pet attributes are the values.
ContentValues values = new ContentValues();
values.put(PetEntry.COLUMN_PET_NAME, "Toto");
values.put(PetEntry.COLUMN_PET_BREED, "Terrier");
values.put(PetEntry.COLUMN_PET_GENDER, PetEntry.GENDER_MALE);
values.put(PetEntry.COLUMN_PET_WEIGHT, 7);
// Insert a new row for Toto into the provider using the ContentResolver.
// Use the {@link PetEntry#CONTENT_URI} to indicate that we want to insert
// into the pets database table.
// Receive the new content URI that will allow us to access Toto's data in the future.
Uri newUri = getContentResolver().insert(PetEntry.CONTENT_URI, values);
}
@Override
public Loader onCreateLoader(int i, Bundle bundle) {
String[] projection = {
PetEntry._ID,
PetEntry.COLUMN_PET_NAME,
PetEntry.COLUMN_PET_BREED};
return new CursorLoader(
this,
PetEntry.CONTENT_URI,
projection,
null,
null,
null
);
}
@Override
public void onLoadFinished(Loader loader, Cursor cursor) {
petCursorAdapter.swapCursor(cursor);
}
@Override
public void onLoaderReset(Loader loader) {
petCursorAdapter.swapCursor(null);
}
/**
* Helper method to delete all pets in the database.
*/
private void deleteAllPets() {
int rowsDeleted = getContentResolver().delete(PetEntry.CONTENT_URI, null, null);
Log.v("CatalogActivity", rowsDeleted + " rows deleted from pet database");
}
}
PetCursorAdapter.Java
package com.example.android.pets;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.TextView;
/**
* {@link PetCursorAdapter} is an adapter for a list or grid view
* that uses a {@link Cursor} of pet data as its data source. This adapter knows
* how to create list items for each row of pet data in the {@link Cursor}.
*/
public class PetCursorAdapter extends CursorAdapter {
/**
* Constructs a new {@link PetCursorAdapter}.
*
* @param context The context
* @param c The cursor from which to get the data.
*/
public PetCursorAdapter(Context context, Cursor c) {
super(context, c, 0 /* flags */);
}
/**
* Makes a new blank list item view. No data is set (or bound) to the views yet.
*
* @param context app context
* @param cursor The cursor from which to get the data. The cursor is already
* moved to the correct position.
* @param parent The parent to which the new view is attached to
* @return the newly created list item view.
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.list_item,parent,false);
}
/**
* This method binds the pet data (in the current row pointed to by cursor) to the given
* list item layout. For example, the name for the current pet can be set on the name TextView
* in the list item layout.
*
* @param view Existing view, returned earlier by newView() method
* @param context app context
* @param cursor The cursor from which to get the data. The cursor is already moved to the
* correct row.
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView tvName = (TextView) view.findViewById(R.id.pet_name);
TextView tvBreed = (TextView) view.findViewById(R.id.pet_breed);
// // Find the columns of pet attributes that we're interested in
// int nameColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_NAME);
// int breedColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_BREED);
//
// // Read the pet attributes from the Cursor for the current pet
// String petName = cursor.getString(nameColumnIndex);
// String petBreed = cursor.getString(breedColumnIndex);
//
// // Update the TextViews with the attributes for the current pet
// nameTextView.setText(petName);
// summaryTextView.setText(petBreed);
//
// Extract properties from cursor
String dogName = cursor.getString(cursor.getColumnIndexOrThrow("name"));
String dogBreed = cursor.getString(cursor.getColumnIndexOrThrow("breed"));
// If the pet breed is empty string or null, then use some default text
// that says "Unknown breed", so the TextView isn't blank.
if (TextUtils.isEmpty(dogBreed)) {
dogBreed = context.getString(R.string.unknown_breed);
}
if (TextUtils.isEmpty(dogName)) {
dogName = "Unknown pet name";
}
// Populate fields with extracted properties
tvName.setText(dogName);
tvBreed.setText(dogBreed);
}
}
menu_catalog.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".CatalogActivity">
<item
android:id="@+id/action_insert_dummy_data"
android:title="@string/action_insert_dummy_data"
app:showAsAction="never" />
<item
android:id="@+id/action_delete_all_entries"
android:title="@string/action_delete_all_entries"
app:showAsAction="never" />
</menu>
***// EditorActivity.Java //***
/*
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;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.example.android.pets.data.PetContract.PetEntry;
import com.example.android.pets.data.PetDbHelper;
/**
* Allows user to create a new pet or edit an existing one.
*/
public class EditorActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks {
private static final int EXISTING_PET_LOADER = 0;
/**
* EditText field to enter the pet's name
*/
private EditText mNameEditText;
/**
* EditText field to enter the pet's breed
*/
private EditText mBreedEditText;
/**
* EditText field to enter the pet's weight
*/
private EditText mWeightEditText;
/**
* EditText field to enter the pet's gender
*/
private Spinner mGenderSpinner;
/**
* Gender of the pet. The possible values are:
* 0 for unknown gender, 1 for male, 2 for female.
*/
private int mGender = 0;
/**
* Content URI for the existing pet (null if it's a new pet)
*/
private Uri mCurrentPetUri;
private boolean mPetHasChanged = false;
private View.OnTouchListener mTouchListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_editor);
// Find all relevant views that we will need to read user input from
mNameEditText = (EditText) findViewById(R.id.edit_pet_name);
mBreedEditText = (EditText) findViewById(R.id.edit_pet_breed);
mWeightEditText = (EditText) findViewById(R.id.edit_pet_weight);
mGenderSpinner = (Spinner) findViewById(R.id.spinner_gender);
Intent intent = getIntent();
mCurrentPetUri = intent.getData();
if (mCurrentPetUri == null) {
setTitle(R.string.editor_activity_title_new_pet);
// Invalidate the options menu, so the "Delete" menu option can be hidden.
// (It doesn't make sense to delete a pet that hasn't been created yet.)
invalidateOptionsMenu();
} else {
setTitle(R.string.editor_activity_title_edit_pet);
}
setupSpinner();
getLoaderManager().initLoader(EXISTING_PET_LOADER, null, this);
// 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.
// 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.
mTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
mPetHasChanged = true;
return false;
}
};
mNameEditText.setOnTouchListener(mTouchListener);
mBreedEditText.setOnTouchListener(mTouchListener);
mWeightEditText.setOnTouchListener(mTouchListener);
mGenderSpinner.setOnTouchListener(mTouchListener);
}
/**
* This method is called after invalidateOptionsMenu(), so that the
* menu can be updated (some menu items can be hidden or made visible).
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
// If this is a new pet, hide the "Delete" menu item.
if (mCurrentPetUri == null) {
MenuItem menuItem = menu.findItem(R.id.action_delete);
menuItem.setVisible(false);
}
return true;
}
private void showUnsavedChangesDialog(DialogInterface.OnClickListener discardButtonClickListener) {
// Create an AlertDialog.Builder and set the message, and click listeners
// for the positive 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();
}
}
});
// Create and show the AlertDialog
AlertDialog alertDialog = builder.create();
alertDialog.show();
}
@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);
}
/**
* Get user input from editor and save new pet into database.
*/
private void savePet() {
// Read from input fields
// Use trim to eliminate leading or trailing white space
String nameString = mNameEditText.getText().toString().trim();
String breedString = mBreedEditText.getText().toString().trim();
String weightString = mWeightEditText.getText().toString().trim();
if (mCurrentPetUri == null &&
TextUtils.isEmpty(nameString) && TextUtils.isEmpty(breedString) &&
TextUtils.isEmpty(weightString) && mGender == PetEntry.GENDER_UNKNOWN) {
return;
}
// Create a ContentValues object where column names are the keys,
// and pet attributes from the editor are the values.
ContentValues values = new ContentValues();
values.put(PetEntry.COLUMN_PET_NAME, nameString);
values.put(PetEntry.COLUMN_PET_BREED, breedString);
values.put(PetEntry.COLUMN_PET_GENDER, mGender);
// If the weight is not provided by the user, don't try to parse the string into an
// integer value. Use 0 by default.
int weight = 0;
if (!TextUtils.isEmpty(weightString)) {
weight = Integer.parseInt(weightString);
}
values.put(PetEntry.COLUMN_PET_WEIGHT, weight);
if (mCurrentPetUri == null) {
// Insert a new pet into the provider, returning the content URI for the new pet.
Uri newUri = getContentResolver().insert(PetEntry.CONTENT_URI, values);
// Show a toast message depending on whether or not the insertion was successful
if (newUri == null) {
// If the new content URI is null, then there was an error with insertion.
Toast.makeText(this, getString(R.string.editor_insert_pet_failed),
Toast.LENGTH_SHORT).show();
} else {
// Otherwise, the insertion was successful and we can display a toast.
Toast.makeText(this, getString(R.string.editor_insert_pet_successful),
Toast.LENGTH_SHORT).show();
}
} else {
// Otherwise this is an EXISTING pet, so update the pet with content URI: mCurrentPetUri
// and pass in the new ContentValues. Pass in null for the selection and selection args
// because mCurrentPetUri will already identify the correct row in the database that
// we want to modify.
int rowsAffected = getContentResolver().update(mCurrentPetUri, values, null, null);
// Show a toast message depending on whether or not the update was successful.
if (rowsAffected == 0) {
// If no rows were affected, then there was an error with the update.
Toast.makeText(this, getString(R.string.editor_update_pet_failed),
Toast.LENGTH_SHORT).show();
} else {
// Otherwise, the update was successful and we can display a toast.
Toast.makeText(this, getString(R.string.editor_update_pet_successful),
Toast.LENGTH_SHORT).show();
}
}
}
/**
* Setup the dropdown spinner that allows the user to select the gender of the pet.
*/
private void setupSpinner() {
// Create adapter for spinner. The list options are from the String array it will use
// the spinner will use the default layout
ArrayAdapter genderSpinnerAdapter = ArrayAdapter.createFromResource(this,
R.array.array_gender_options, android.R.layout.simple_spinner_item);
// Specify dropdown layout style - simple list view with 1 item per line
genderSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
// Apply the adapter to the spinner
mGenderSpinner.setAdapter(genderSpinnerAdapter);
// Set the integer mSelected to the constant values
mGenderSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView parent, View view, int position, long id) {
String selection = (String) parent.getItemAtPosition(position);
/**
* for null check:- !TextUtils.isEmpty(selection
*/
if (!TextUtils.isEmpty(selection)) {
if (selection.equals(getString(R.string.gender_male))) {
mGender = PetEntry.GENDER_MALE; // Male
} else if (selection.equals(getString(R.string.gender_female))) {
mGender = PetEntry.GENDER_FEMALE; // Female
} else {
mGender = PetEntry.GENDER_UNKNOWN; // Unknown
}
}
}
// Because AdapterView is an abstract class, onNothingSelected must be defined
@Override
public void onNothingSelected(AdapterView parent) {
mGender = 0; // Unknown
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu options from the res/menu/menu_editor.xml file.
// This adds menu items to the app bar.
getMenuInflater().inflate(R.menu.menu_editor, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// User clicked on a menu option in the app bar overflow menu
switch (item.getItemId()) {
// Respond to a click on the "Save" menu option
case R.id.action_save:
savePet();
finish();
return true;
// Respond to a click on the "Delete" menu option
case R.id.action_delete:
// Pop up confirmation dialog for deletion
showDeleteConfirmationDialog();
return true;
// Respond to a click on the "Up" arrow button in the app bar
case android.R.id.home:
// 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);
}
@Override
public Loader onCreateLoader(int i, Bundle bundle) {
// Since the editor shows all pet attributes, define a projection that contains
// all columns from the pet table
String[] projection = {
PetEntry._ID,
PetEntry.COLUMN_PET_NAME,
PetEntry.COLUMN_PET_BREED,
PetEntry.COLUMN_PET_GENDER,
PetEntry.COLUMN_PET_WEIGHT};
// This loader will execute the ContentProvider's query method on a background thread
return new CursorLoader(this, // Parent activity context
mCurrentPetUri, // Query the content URI for the current pet
projection, // Columns to include in the resulting Cursor
null, // No selection clause
null, // No selection arguments
null); // Default sort order
}
@Override
public void onLoadFinished(Loader loader, Cursor cursor) {
// Bail early if the cursor is null or there is less than 1 row in the cursor
if (cursor == null || cursor.getCount() < 1) {
return;
}
// Proceed with moving to the first row of the cursor and reading data from it
// (This should be the only row in the cursor)
if (cursor.moveToFirst()) {
// Find the columns of pet attributes that we're interested in
int nameColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_NAME);
int breedColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_BREED);
int genderColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_GENDER);
int weightColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_WEIGHT);
// Extract out the value from the Cursor for the given column index
String name = cursor.getString(nameColumnIndex);
String breed = cursor.getString(breedColumnIndex);
int gender = cursor.getInt(genderColumnIndex);
int weight = cursor.getInt(weightColumnIndex);
// Update the views on the screen with the values from the database
mNameEditText.setText(name);
mBreedEditText.setText(breed);
mWeightEditText.setText(Integer.toString(weight));
// Gender is a dropdown spinner, so map the constant value from the database
// into one of the dropdown options (0 is Unknown, 1 is Male, 2 is Female).
// Then call setSelection() so that option is displayed on screen as the current selection.
switch (gender) {
case PetEntry.GENDER_MALE:
mGenderSpinner.setSelection(1);
break;
case PetEntry.GENDER_FEMALE:
mGenderSpinner.setSelection(2);
break;
default:
mGenderSpinner.setSelection(0);
break;
}
}
}
@Override
public void onLoaderReset(Loader loader) {
// If the loader is invalidated, clear out all the data from the input fields.
mNameEditText.setText("");
mBreedEditText.setText("");
mWeightEditText.setText("");
mGenderSpinner.setSelection(0); // Select "Unknown" gender
}
private void showDeleteConfirmationDialog() {
// 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.delete_dialog_msg);
builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User clicked the "Delete" button, so delete the pet.
deletePet();
}
});
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User clicked the "Cancel" button, so dismiss the dialog
// and continue editing the pet.
if (dialog != null) {
dialog.dismiss();
}
}
});
// Create and show the AlertDialog
AlertDialog alertDialog = builder.create();
alertDialog.show();
}
/**
* Perform the deletion of the pet in the database.
*/
private void deletePet() {
// Only perform the delete if this is an existing pet.
if (mCurrentPetUri != null) {
// Call the ContentResolver to delete the pet at the given content URI.
// Pass in null for the selection and selection args because the mCurrentPetUri
// content URI already identifies the pet that we want.
int rowsDeleted = getContentResolver().delete(mCurrentPetUri, null, null);
// Show a toast message depending on whether or not the delete was successful.
if (rowsDeleted == 0) {
// If no rows were deleted, then there was an error with the delete.
Toast.makeText(this, getString(R.string.editor_delete_pet_failed),
Toast.LENGTH_SHORT).show();
} else {
// Otherwise, the delete was successful and we can display a toast.
Toast.makeText(this, getString(R.string.editor_delete_pet_successful),
Toast.LENGTH_SHORT).show();
}
}
// Close the activity
finish();
}
}
menu_editor.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".EditorActivity">
<item
android:id="@+id/action_save"
android:title="@string/action_save"
android:icon="@drawable/ic_done"
app:showAsAction="always" />
<item
android:id="@+id/action_delete"
android:title="@string/action_delete"
app:showAsAction="never" />
</menu>
***//Sql Database Classes//**
PetContract.java
package com.example.android.pets.data;
import android.content.ContentResolver;
import android.net.Uri;
import android.provider.BaseColumns;
public final class PetContract {
public PetContract() {
}
public static final String CONTENT_AUTHORITY = "com.example.android.pets";
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
public static final String PATH_PETS = "pets";
public static final class PetEntry implements BaseColumns{
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, PATH_PETS);
public static final String TABLE_NAME = "pets";
public final static String _ID = BaseColumns._ID;
public final static String COLUMN_PET_NAME = "name";
public final static String COLUMN_PET_BREED = "breed";
public final static String COLUMN_PET_GENDER = "gender";
public final static String COLUMN_PET_WEIGHT = "weight";
/**
* The MIME type of the {@link #CONTENT_URI} for a list of pets.
*/
public static final String CONTENT_LIST_TYPE =
ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PETS;
/**
* The MIME type of the {@link #CONTENT_URI} for a single pet.
*/
public static final String CONTENT_ITEM_TYPE =
ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PETS;
/**
* Constants for Gender
*/
public static final int GENDER_UNKNOWN = 0;
public static final int GENDER_MALE = 1;
public static final int GENDER_FEMALE = 2;
/**
* Returns whether or not the given gender is {@link #GENDER_UNKNOWN}, {@link #GENDER_MALE},
* or {@link #GENDER_FEMALE}.
*/
public static boolean isValidGender(int gender) {
if (gender == GENDER_UNKNOWN || gender == GENDER_MALE || gender == GENDER_FEMALE) {
return true;
}
return false;
}
}
}
PetDbHelper.java
package com.example.android.pets.data; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import com.example.android.pets.data.PetContract.PetEntry; public class PetDbHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "shelter.db"; private static final int DATABASE_VERSION = 1; public PetDbHelper(Context context){ super(context,DATABASE_NAME,null,DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { // Create a String that contains the SQL statement to create the pets table String SQL_CREATE_PETS_TABLE = "CREATE TABLE " + PetEntry.TABLE_NAME + " (" + PetEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + PetEntry.COLUMN_PET_NAME + " TEXT NOT NULL, " + PetEntry.COLUMN_PET_BREED + " TEXT, " + PetEntry.COLUMN_PET_GENDER + " INTEGER NOT NULL, " + PetEntry.COLUMN_PET_WEIGHT + " INTEGER NOT NULL DEFAULT 0);"; // String SQL_CREATE_PETS_TABLE = "CREATE TABLE "+ PetEntry.TABLE_NAME + "(" // + PetEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + // PetEntry.COLUMN_PET_NAME + " TEXT NOT NULL, "+ // PetEntry.COLUMN_PET_BREED + " TEXT, "+ // PetEntry.COLUMN_PET_GENDER + " INTEGER NOT NULL, "+ // PetEntry.COLUMN_PET_WEIGHT + " INTEGER NOT NULL DEFAULT 0);"; // db.execSQL(SQL_CREATE_PETS_TABLE); } @Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { }
}
PetProvider.java
package com.example.android.pets.data;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
public class PetProvider extends ContentProvider {
/** Tag for the log messages */
public static final String LOG_TAG = PetProvider.class.getSimpleName();
private PetDbHelper petDbHelper;
///////////////
/** URI matcher code for the content URI for the pets table */
private static final int PETS = 100;
/** URI matcher code for the content URI for a single pet in the pets table */
private static final int PET_ID = 101;
/**
* UriMatcher object to match a content URI to a corresponding code.
* The input passed into the constructor represents the code to return for the root URI.
* It's common to use NO_MATCH as the input for this case.
*/
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// Static initializer. This is run the first time anything is called from this class.
static {
// The calls to addURI() go here, for all of the content URI patterns that the provider
// should recognize. All paths added to the UriMatcher have a corresponding code to return
// when a match is found.
sUriMatcher.addURI(PetContract.CONTENT_AUTHORITY, PetContract.PATH_PETS, PETS);
sUriMatcher.addURI(PetContract.CONTENT_AUTHORITY, PetContract.PATH_PETS + "/#", PET_ID);
}
//////////////////////
@Override
public boolean onCreate() {
petDbHelper = new PetDbHelper(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// Get readable database
SQLiteDatabase database = petDbHelper.getReadableDatabase();
// This cursor will hold the result of the query
Cursor cursor = null;
// Figure out if the URI matcher can match the URI to a specific code
int match = sUriMatcher.match(uri);
switch (match) {
case PETS:
// For the PETS code, query the pets table directly with the given
// projection, selection, selection arguments, and sort order. The cursor
// could contain multiple rows of the pets table.
cursor = database.query(PetContract.PetEntry.TABLE_NAME, projection, selection, selectionArgs,
null, null, sortOrder);
break;
case PET_ID:
// For the PET_ID code, extract out the ID from the URI.
// For an example URI such as "content://com.example.android.pets/pets/3",
// the selection will be "_id=?" and the selection argument will be a
// String array containing the actual ID of 3 in this case.
//
// For every "?" in the selection, we need to have an element in the selection
// arguments that will fill in the "?". Since we have 1 question mark in the
// selection, we have 1 String in the selection arguments' String array.
selection = PetContract.PetEntry._ID + "=?"; //_ID =?
selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) }; //new String[]{"3"}
// This will perform a query on the pets table where the _id equals 3 to return a
// Cursor containing that row of the table.
cursor = database.query(PetContract.PetEntry.TABLE_NAME, projection, selection, selectionArgs,
null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Cannot query unknown URI " + uri);
}
//set notification on the uri to know data changed for update the cursor.
cursor.setNotificationUri(getContext().getContentResolver(),uri);
return cursor;
}
@Override
public String getType(Uri uri) {
final int match = sUriMatcher.match(uri);
switch (match) {
case PETS:
return PetContract.PetEntry.CONTENT_LIST_TYPE;
case PET_ID:
return PetContract.PetEntry.CONTENT_ITEM_TYPE;
default:
throw new IllegalStateException("Unknown URI " + uri + " with match " + match);
}
}
@Override
public Uri insert(Uri uri, ContentValues contentValues) {
final int match = sUriMatcher.match(uri);
switch (match) {
case PETS:
return insertPet(uri, contentValues);
default:
throw new IllegalArgumentException("Insertion is not supported for " + uri);
}
}
/**
* Insert a pet into the database with the given content values. Return the new content URI
* for that specific row in the database.
*/
private Uri insertPet(Uri uri, ContentValues values) {
// Check that the name is not null
String name = values.getAsString(PetContract.PetEntry.COLUMN_PET_NAME);
if (name == null) {
throw new IllegalArgumentException("Pet requires a name");
}
// Check that the gender is valid
Integer gender = values.getAsInteger(PetContract.PetEntry.COLUMN_PET_GENDER);
if (gender == null || !PetContract.PetEntry.isValidGender(gender)) {
throw new IllegalArgumentException("Pet requires valid gender");
}
// If the weight is provided, check that it's greater than or equal to 0 kg
Integer weight = values.getAsInteger(PetContract.PetEntry.COLUMN_PET_WEIGHT);
if (weight != null && weight < 0) {
throw new IllegalArgumentException("Pet requires valid weight");
}
// No need to check the breed, any value is valid (including null).
// Get writeable database
SQLiteDatabase database = petDbHelper.getWritableDatabase();
// Insert the new pet with the given values
long id = database.insert(PetContract.PetEntry.TABLE_NAME, null, values);
// If the ID is -1, then the insertion failed. Log an error and return null.
if (id == -1) {
Log.e(LOG_TAG, "Failed to insert row for " + uri);
return null;
}
//Notify all the listener data has changed
getContext().getContentResolver().notifyChange(uri,null);
// Return the new URI with the ID (of the newly inserted row) appended at the end
return ContentUris.withAppendedId(uri, id);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Get writeable database
SQLiteDatabase database = petDbHelper.getWritableDatabase();
// Track the number of rows that were deleted
int rowsDeleted;
final int match = sUriMatcher.match(uri);
switch (match) {
case PETS:
// Delete all rows that match the selection and selection args
// For case PETS:
rowsDeleted = database.delete(PetContract.PetEntry.TABLE_NAME, selection, selectionArgs);
break;
case PET_ID:
// Delete a single row given by the ID in the URI
selection = PetContract.PetEntry._ID + "=?";
selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) };
// For case PET_ID:
// Delete a single row given by the ID in the URI
rowsDeleted = database.delete(PetContract.PetEntry.TABLE_NAME, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Deletion is not supported for " + uri);
}
// If 1 or more rows were deleted, then notify all listeners that the data at the
// given URI has changed
if (rowsDeleted != 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
// Return the number of rows deleted
return rowsDeleted;
}
@Override
public int update(Uri uri, ContentValues contentValues, String selection,
String[] selectionArgs) {
final int match = sUriMatcher.match(uri);
switch (match) {
case PETS:
return updatePet(uri, contentValues, selection, selectionArgs);
case PET_ID:
// For the PET_ID code, extract out the ID from the URI,
// so we know which row to update. Selection will be "_id=?" and selection
// arguments will be a String array containing the actual ID.
selection = PetContract.PetEntry._ID + "=?";
selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) };
return updatePet(uri, contentValues, selection, selectionArgs);
default:
throw new IllegalArgumentException("Update is not supported for " + uri);
}
}
/**
* Update pets in the database with the given content values. Apply the changes to the rows
* specified in the selection and selection arguments (which could be 0 or 1 or more pets).
* Return the number of rows that were successfully updated.
*/
private int updatePet(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// If the {@link PetEntry#COLUMN_PET_NAME} key is present,
// check that the name value is not null.
if (values.containsKey(PetContract.PetEntry.COLUMN_PET_NAME)) {
String name = values.getAsString(PetContract.PetEntry.COLUMN_PET_NAME);
if (name == null) {
throw new IllegalArgumentException("Pet requires a name");
}
}
// If the {@link PetEntry#COLUMN_PET_GENDER} key is present,
// check that the gender value is valid.
if (values.containsKey(PetContract.PetEntry.COLUMN_PET_GENDER)) {
Integer gender = values.getAsInteger(PetContract.PetEntry.COLUMN_PET_GENDER);
if (gender == null || !PetContract.PetEntry.isValidGender(gender)) {
throw new IllegalArgumentException("Pet requires valid gender");
}
}
// If the {@link PetEntry#COLUMN_PET_WEIGHT} key is present,
// check that the weight value is valid.
if (values.containsKey(PetContract.PetEntry.COLUMN_PET_WEIGHT)) {
// Check that the weight is greater than or equal to 0 kg
Integer weight = values.getAsInteger(PetContract.PetEntry.COLUMN_PET_WEIGHT);
if (weight != null && weight < 0) {
throw new IllegalArgumentException("Pet requires valid weight");
}
}
// No need to check the breed, any value is valid (including null).
// If there are no values to update, then don't try to update the database
if (values.size() == 0) {
return 0;
}
// Otherwise, get writeable database to update the data
SQLiteDatabase database = petDbHelper.getWritableDatabase();
// Perform the update on the database and get the number of rows affected
int rowsUpdated = database.update(PetContract.PetEntry.TABLE_NAME, values, selection, selectionArgs);
// If 1 or more rows were updated, then notify all listeners that the data at the
// given URI has changed
if (rowsUpdated != 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
// Return the number of rows updated
return rowsUpdated;
}
}
arrays.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- These are the options displayed in the gender drop-down Spinner -->
<string-array name="array_gender_options">
<item>@string/gender_unknown</item>
<item>@string/gender_male</item>
<item>@string/gender_female</item>
</string-array>
</resources>
Strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Name for the application [CHAR LIMIT=12] -->
<string name="app_name">Pets</string>
<!-- Label for overflow menu option that inserts fake pet data into the app [CHAR LIMIT=20] -->
<string name="action_insert_dummy_data">Insert Dummy Data</string>
<!-- Label for overflow menu option that deletes all pet data in the app [CHAR LIMIT=20] -->
<string name="action_delete_all_entries">Delete All Pets</string>
<!-- Title for the activity to add a new pet [CHAR LIMIT=20] -->
<string name="editor_activity_title_new_pet">Add a Pet</string>
<!-- Title for the activity to add a new pet [CHAR LIMIT=20] -->
<string name="editor_activity_title_edit_pet">Edit Pet</string>
<!-- Toast message in editor when current pet was successfully updated [CHAR LIMIT=NONE] -->
<string name="editor_update_pet_successful">Pet updated</string>
<!-- Toast message in editor when current pet has failed to be updated [CHAR LIMIT=NONE] -->
<string name="editor_update_pet_failed">Error with updating pet</string>
<!-- Label for editor menu option to save pet and leave editor [CHAR LIMIT=20] -->
<string name="action_save">Save</string>
<!-- Label for editor overflow menu option that deletes the current pet [CHAR LIMIT=20] -->
<string name="action_delete">Delete</string>
<!-- Label for overview category of attributes in the editor [CHAR LIMIT=30] -->
<string name="category_overview">Overview</string>
<!-- Label for gender information in the editor [CHAR LIMIT=30] -->
<string name="category_gender">Gender</string>
<!-- Label for measurement information in the editor [CHAR LIMIT=30] -->
<string name="category_measurement">Measurement</string>
<!-- Text hint for name field in the editor [CHAR LIMIT=30] -->
<string name="hint_pet_name">Name</string>
<!-- Text hint for breed field in the editor [CHAR LIMIT=30] -->
<string name="hint_pet_breed">Breed</string>
<!-- Text hint for name field in the editor [CHAR LIMIT=30] -->
<string name="hint_pet_weight">Weight</string>
<!-- Units for weight of the pet (kilograms) [CHAR LIMIT=5] -->
<string name="unit_pet_weight">kg</string>
<!-- Label for dropdown menu option if the pet's gender is unknown [CHAR LIMIT=20] -->
<string name="gender_unknown">Unknown</string>
<!-- Label for dropdown menu option if the pet is male [CHAR LIMIT=20] -->
<string name="gender_male">Male</string>
<!-- Label for dropdown menu option if the pet is female [CHAR LIMIT=20] -->
<string name="gender_female">Female</string>
<!-- Toast message in editor when new pet has been successfully inserted [CHAR LIMIT=NONE] -->
<string name="editor_insert_pet_successful">Pet saved</string>
<!-- Toast message in editor when new pet has failed to be inserted [CHAR LIMIT=NONE] -->
<string name="editor_insert_pet_failed">Error with saving pet</string>
<!-- Title text for the empty view, which describes the empty dog house image [CHAR LIMIT=50] -->
<string name="empty_view_title_text">It\'s a bit lonely here...</string>
<!-- Subtitle text for the empty view that prompts the user to add a pet [CHAR LIMIT=50] -->
<string name="empty_view_subtitle_text">Get started by adding a pet</string>
!-- Dialog message when user is leaving editor but hasn't saved changes [CHAR LIMIT=NONE] -->
<string name="unsaved_changes_dialog_msg">Discard your changes and quit editing?</string>
<!-- Dialog button text for the option to discard a user's changes [CHAR LIMIT=20] -->
<string name="discard">Discard</string>
<!-- Dialog button text for the option to keep editing the current pet [CHAR LIMIT=20] -->
<string name="keep_editing">Keep Editing</string>
<!-- Toast message in editor when current pet was successfully deleted [CHAR LIMIT=NONE] -->
<string name="editor_delete_pet_successful">Pet deleted</string>
<!-- Toast message in editor when current pet has failed to be deleted [CHAR LIMIT=NONE] -->
<string name="editor_delete_pet_failed">Error with deleting pet</string>
<!-- Dialog message to ask the user to confirm deleting the current pet [CHAR LIMIT=NONE] -->
<string name="delete_dialog_msg">Delete this pet?</string>
<!-- Dialog button text for the option to confirm deleting the current pet [CHAR LIMIT=20] -->
<string name="delete">Delete</string>
<!-- Dialog button text for the option to cancel deletion of the current pet [CHAR LIMIT=20] -->
<string name="cancel">Cancel</string>
<!-- Label for the pet's breed if the breed is unknown [CHAR LIMIT=20] -->
<string name="unknown_breed">Unknown breed</string>
</resources>
Styles.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<!-- Theme of the editor -->
<style name="EditorTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/editorColorPrimary</item>
<item name="colorPrimaryDark">@color/editorColorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<!-- Style for a category in the editor -->
<style name="CategoryStyle">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">0dp</item>
<item name="android:layout_weight">1</item>
<item name="android:paddingTop">16dp</item>
<item name="android:textColor">@color/colorAccent</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textAppearance">?android:textAppearanceSmall</item>
</style>
<!-- Style for an EditText field in the editor -->
<style name="EditorFieldStyle">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">match_parent</item>
<item name="android:fontFamily">sans-serif-light</item>
<item name="android:textAppearance">?android:textAppearanceMedium</item>
</style>
<!-- Style for the measurement units for an EditText field in the editor -->
<style name="EditorUnitsStyle">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_alignParentRight">true</item>
<item name="android:paddingRight">16dp</item>
<item name="android:paddingTop">16dp</item>
<item name="android:textAppearance">?android:textAppearanceSmall</item>
</style>
</resources>
dimens.xml
<resources>
<!-- Margin around the floating action button in CatalogActivity -->
<dimen name="fab_margin">16dp</dimen>
<!-- Common margin value used throughout the app -->
<dimen name="activity_margin">16dp</dimen>
</resources>
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- The app branding color for the app bar -->
<color name="colorPrimary">#F0514B</color>
<!-- Darker variant for the status bar and contextual app bars -->
<color name="colorPrimaryDark">#C0403C</color>
<!-- Accent color for UI controls like checkboxes and text fields -->
<color name="colorAccent">#F0514B</color>
<!-- Primary color for the editor -->
<color name="editorColorPrimary">#2D3640</color>
<!-- Primary dark color for the editor -->
<color name="editorColorPrimaryDark">#394450</color>
</resources>
0 Comments