From 7d0e356e7ab4d37ecb9e514ed45731367215078e Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Tue, 19 Jul 2016 09:44:10 -0700 Subject: [PATCH 01/23] 1.7 Add ContentProvider --- app/src/main/AndroidManifest.xml | 4 ++ .../android/pets/data/PetProvider.java | 61 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 app/src/main/java/com/example/android/pets/data/PetProvider.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7b6a6914..5ba2a60a 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -37,6 +37,10 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".CatalogActivity" /> + \ No newline at end of file diff --git a/app/src/main/java/com/example/android/pets/data/PetProvider.java b/app/src/main/java/com/example/android/pets/data/PetProvider.java new file mode 100644 index 00000000..4eefe5e7 --- /dev/null +++ b/app/src/main/java/com/example/android/pets/data/PetProvider.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.pets.data; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +/** + * {@link ContentProvider} for Pets app. + */ +public class PetProvider extends ContentProvider { + + /** Database helper object */ + private PetDbHelper mDbHelper; + + @Override + public boolean onCreate() { + mDbHelper = new PetDbHelper(getContext()); + return true; + } + + @Override + public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) { + return null; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues contentValues) { + return null; + } + + @Override + public int delete(Uri uri, String s, String[] strings) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues contentValues, String s, String[] strings) { + return 0; + } +} From 07f386681f2f18726bb6ae72eec9980131135b37 Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Thu, 21 Jul 2016 13:29:57 -0700 Subject: [PATCH 02/23] 1.8 Add constants for pet content URIs --- .../android/pets/data/PetContract.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/src/main/java/com/example/android/pets/data/PetContract.java b/app/src/main/java/com/example/android/pets/data/PetContract.java index 8fe4990d..a182d550 100755 --- a/app/src/main/java/com/example/android/pets/data/PetContract.java +++ b/app/src/main/java/com/example/android/pets/data/PetContract.java @@ -15,6 +15,7 @@ */ package com.example.android.pets.data; +import android.net.Uri; import android.provider.BaseColumns; /** @@ -26,12 +27,37 @@ public final class PetContract { // give it an empty constructor. private PetContract() {} + /** + * The "Content authority" is a name for the entire content provider, similar to the + * relationship between a domain name and its website. A convenient string to use for the + * content authority is the package name for the app, which is guaranteed to be unique on the + * device. + */ + public static final String CONTENT_AUTHORITY = "com.example.android.pets"; + + /** + * Use CONTENT_AUTHORITY to create the base of all URI's which apps will use to contact + * the content provider. + */ + public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY); + + /** + * Possible path (appended to base content URI for possible URI's) + * For instance, content://com.example.android.pets/pets/ is a valid path for + * looking at pet data. content://com.example.android.pets/staff/ will fail, + * as the ContentProvider hasn't been given any information on what to do with "staff". + */ + public static final String PATH_PETS = "pets"; + /** * Inner class that defines constant values for the pets database table. * Each entry in the table represents a single pet. */ public static final class PetEntry implements BaseColumns { + /** The content URI to access the pet data in the provider */ + public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, PATH_PETS); + /** Name of database table for pets */ public final static String TABLE_NAME = "pets"; From b34ac1fa3ebb3cf525b6dca88854d041ed37fdeb Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Tue, 19 Jul 2016 10:06:13 -0700 Subject: [PATCH 03/23] 1.9 Add UriMatcher to ContentProvider --- .../android/pets/data/PetProvider.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/app/src/main/java/com/example/android/pets/data/PetProvider.java b/app/src/main/java/com/example/android/pets/data/PetProvider.java index 4eefe5e7..2e01ff96 100644 --- a/app/src/main/java/com/example/android/pets/data/PetProvider.java +++ b/app/src/main/java/com/example/android/pets/data/PetProvider.java @@ -17,6 +17,7 @@ import android.content.ContentProvider; import android.content.ContentValues; +import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; @@ -25,6 +26,40 @@ */ public class PetProvider extends ContentProvider { + /** 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. + + // The content URI of the form "content://com.example.android.pets/pets" will map to the + // integer code {@link #PETS}. This URI is used to provide access to MULTIPLE rows + // of the pets table. + sUriMatcher.addURI(PetContract.CONTENT_AUTHORITY, PetContract.PATH_PETS, PETS); + + // The content URI of the form "content://com.example.android.pets/pets/#" will map to the + // integer code {@link #PET_ID}. This URI is used to provide access to ONE single row + // of the pets table. + // + // In this case, the "#" wildcard is used where "#" can be substituted for an integer. + // For example, "content://com.example.android.pets/pets/3" matches, but + // "content://com.example.android.pets/pets" (without a number at the end) doesn't match. + sUriMatcher.addURI(PetContract.CONTENT_AUTHORITY, PetContract.PATH_PETS + "/#", PET_ID); + } + /** Database helper object */ private PetDbHelper mDbHelper; From b295107d56c5887544d55f536be74e84b1b1d454 Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Tue, 19 Jul 2016 16:22:20 -0700 Subject: [PATCH 04/23] 1.10 Implement ContentProvider query() method --- .../android/pets/data/PetProvider.java | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/android/pets/data/PetProvider.java b/app/src/main/java/com/example/android/pets/data/PetProvider.java index 2e01ff96..71347912 100644 --- a/app/src/main/java/com/example/android/pets/data/PetProvider.java +++ b/app/src/main/java/com/example/android/pets/data/PetProvider.java @@ -16,11 +16,15 @@ 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 com.example.android.pets.data.PetContract.PetEntry; + /** * {@link ContentProvider} for Pets app. */ @@ -70,8 +74,45 @@ public boolean onCreate() { } @Override - public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) { - return null; + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + // Get readable database + SQLiteDatabase database = mDbHelper.getReadableDatabase(); + + // This cursor will hold the result of the query + Cursor cursor; + + // 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(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 = PetEntry._ID + "=?"; + selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) }; + + // 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(PetEntry.TABLE_NAME, projection, selection, selectionArgs, + null, null, sortOrder); + break; + default: + throw new IllegalArgumentException("Cannot query unknown URI " + uri); + } + return cursor; } @Override From bf44f2658d0be2e24e48222259b9252a13b4d3c7 Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Thu, 21 Jul 2016 16:37:01 -0700 Subject: [PATCH 05/23] 1.11 Query the provider using the pet content URI --- .../example/android/pets/CatalogActivity.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/example/android/pets/CatalogActivity.java b/app/src/main/java/com/example/android/pets/CatalogActivity.java index 87d8a38c..a40f4d11 100755 --- a/app/src/main/java/com/example/android/pets/CatalogActivity.java +++ b/app/src/main/java/com/example/android/pets/CatalogActivity.java @@ -69,9 +69,6 @@ protected void onStart() { * the pets database. */ private void displayDatabaseInfo() { - // Create and/or open a database to read from it - SQLiteDatabase db = mDbHelper.getReadableDatabase(); - // Define a projection that specifies which columns from the database // you will actually use after this query. String[] projection = { @@ -81,15 +78,14 @@ private void displayDatabaseInfo() { PetEntry.COLUMN_PET_GENDER, PetEntry.COLUMN_PET_WEIGHT }; - // Perform a query on the pets table - Cursor cursor = db.query( - PetEntry.TABLE_NAME, // The table to query - projection, // The columns to return - null, // The columns for the WHERE clause - null, // The values for the WHERE clause - null, // Don't group the rows - null, // Don't filter by row groups - null); // The sort order + // Perform a query on the provider using the ContentResolver. + // Use the {@link PetEntry#CONTENT_URI} to access the pet data. + Cursor cursor = getContentResolver().query( + PetEntry.CONTENT_URI, // The content URI of the words table + projection, // The columns to return for each row + null, // Selection criteria + null, // Selection criteria + null); // The sort order for the returned rows TextView displayView = (TextView) findViewById(R.id.text_view_pet); From c64812e3245758ef63496fc7945082d6268d0abb Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Tue, 19 Jul 2016 17:03:53 -0700 Subject: [PATCH 06/23] 1.12 Implement and use ContentProvider insert() method --- .../example/android/pets/CatalogActivity.java | 26 ++++----------- .../example/android/pets/EditorActivity.java | 25 ++++++--------- .../android/pets/data/PetProvider.java | 32 ++++++++++++++++++- app/src/main/res/values/strings.xml | 6 ++++ 4 files changed, 53 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/example/android/pets/CatalogActivity.java b/app/src/main/java/com/example/android/pets/CatalogActivity.java index a40f4d11..b34e1828 100755 --- a/app/src/main/java/com/example/android/pets/CatalogActivity.java +++ b/app/src/main/java/com/example/android/pets/CatalogActivity.java @@ -18,7 +18,7 @@ import android.content.ContentValues; import android.content.Intent; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AppCompatActivity; @@ -28,16 +28,12 @@ import android.widget.TextView; import com.example.android.pets.data.PetContract.PetEntry; -import com.example.android.pets.data.PetDbHelper; /** * Displays list of pets that were entered and stored in the app. */ public class CatalogActivity extends AppCompatActivity { - /** Database helper that will provide us access to the database */ - private PetDbHelper mDbHelper; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -52,10 +48,6 @@ public void onClick(View view) { startActivity(intent); } }); - - // To access our database, we instantiate our subclass of SQLiteOpenHelper - // and pass the context, which is the current activity. - mDbHelper = new PetDbHelper(this); } @Override @@ -138,9 +130,6 @@ private void displayDatabaseInfo() { * Helper method to insert hardcoded pet data into the database. For debugging purposes only. */ private void insertPet() { - // Gets the database in write mode - SQLiteDatabase db = mDbHelper.getWritableDatabase(); - // Create a ContentValues object where column names are the keys, // and Toto's pet attributes are the values. ContentValues values = new ContentValues(); @@ -149,14 +138,11 @@ private void insertPet() { values.put(PetEntry.COLUMN_PET_GENDER, PetEntry.GENDER_MALE); values.put(PetEntry.COLUMN_PET_WEIGHT, 7); - // Insert a new row for Toto in the database, returning the ID of that new row. - // The first argument for db.insert() is the pets table name. - // The second argument provides the name of a column in which the framework - // can insert NULL in the event that the ContentValues is empty (if - // this is set to "null", then the framework will not insert a row when - // there are no values). - // The third argument is the ContentValues object containing the info for Toto. - long newRowId = db.insert(PetEntry.TABLE_NAME, null, values); + // 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 diff --git a/app/src/main/java/com/example/android/pets/EditorActivity.java b/app/src/main/java/com/example/android/pets/EditorActivity.java index 78fc37b9..0127757b 100755 --- a/app/src/main/java/com/example/android/pets/EditorActivity.java +++ b/app/src/main/java/com/example/android/pets/EditorActivity.java @@ -16,7 +16,7 @@ package com.example.android.pets; import android.content.ContentValues; -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; @@ -31,7 +31,6 @@ 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. @@ -121,12 +120,6 @@ private void insertPet() { String weightString = mWeightEditText.getText().toString().trim(); int weight = Integer.parseInt(weightString); - // Create database helper - PetDbHelper mDbHelper = new PetDbHelper(this); - - // Gets the database in write mode - SQLiteDatabase db = mDbHelper.getWritableDatabase(); - // Create a ContentValues object where column names are the keys, // and pet attributes from the editor are the values. ContentValues values = new ContentValues(); @@ -135,16 +128,18 @@ private void insertPet() { values.put(PetEntry.COLUMN_PET_GENDER, mGender); values.put(PetEntry.COLUMN_PET_WEIGHT, weight); - // Insert a new row for pet in the database, returning the ID of that new row. - long newRowId = db.insert(PetEntry.TABLE_NAME, null, values); + // 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 (newRowId == -1) { - // If the row ID is -1, then there was an error with insertion. - Toast.makeText(this, "Error with saving pet", Toast.LENGTH_SHORT).show(); + 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 with the row ID. - Toast.makeText(this, "Pet saved with row id: " + newRowId, Toast.LENGTH_SHORT).show(); + // 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(); } } diff --git a/app/src/main/java/com/example/android/pets/data/PetProvider.java b/app/src/main/java/com/example/android/pets/data/PetProvider.java index 71347912..268eea27 100644 --- a/app/src/main/java/com/example/android/pets/data/PetProvider.java +++ b/app/src/main/java/com/example/android/pets/data/PetProvider.java @@ -22,6 +22,7 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; +import android.util.Log; import com.example.android.pets.data.PetContract.PetEntry; @@ -30,6 +31,9 @@ */ public class PetProvider extends ContentProvider { + /** Tag for the log messages */ + public static final String LOG_TAG = PetProvider.class.getSimpleName(); + /** URI matcher code for the content URI for the pets table */ private static final int PETS = 100; @@ -122,7 +126,33 @@ public String getType(Uri uri) { @Override public Uri insert(Uri uri, ContentValues contentValues) { - return null; + 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) { + // Get writeable database + SQLiteDatabase database = mDbHelper.getWritableDatabase(); + + // Insert the new pet with the given values + long id = database.insert(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; + } + + // Return the new URI with the ID (of the newly inserted row) appended at the end + return ContentUris.withAppendedId(uri, id); } @Override diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c086a8e5..47e68684 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,12 @@ Delete + + Pet saved + + + Error with saving pet + Overview From 6861be56c0c5c3673f0a0b8a04922bfee823582b Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Thu, 21 Jul 2016 17:39:23 -0700 Subject: [PATCH 07/23] 1.13 Add input validation to ContentProvider insert() method --- .../android/pets/data/PetContract.java | 11 ++++++++++ .../android/pets/data/PetProvider.java | 20 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/app/src/main/java/com/example/android/pets/data/PetContract.java b/app/src/main/java/com/example/android/pets/data/PetContract.java index a182d550..a3f5a55c 100755 --- a/app/src/main/java/com/example/android/pets/data/PetContract.java +++ b/app/src/main/java/com/example/android/pets/data/PetContract.java @@ -105,6 +105,17 @@ public static final class PetEntry implements BaseColumns { 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; + } } } diff --git a/app/src/main/java/com/example/android/pets/data/PetProvider.java b/app/src/main/java/com/example/android/pets/data/PetProvider.java index 268eea27..f98a2a5d 100644 --- a/app/src/main/java/com/example/android/pets/data/PetProvider.java +++ b/app/src/main/java/com/example/android/pets/data/PetProvider.java @@ -140,6 +140,26 @@ public Uri insert(Uri uri, ContentValues contentValues) { * 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(PetEntry.COLUMN_PET_NAME); + if (name == null) { + throw new IllegalArgumentException("Pet requires a name"); + } + + // Check that the gender is valid + Integer gender = values.getAsInteger(PetEntry.COLUMN_PET_GENDER); + if (gender == null || !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(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 = mDbHelper.getWritableDatabase(); From a1bcc0c35b19ee0c5b85ba295312e439bcf74bd8 Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Tue, 19 Jul 2016 17:41:30 -0700 Subject: [PATCH 08/23] 1.14 Implement ContentProvider update() method --- .../android/pets/data/PetProvider.java | 66 ++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/android/pets/data/PetProvider.java b/app/src/main/java/com/example/android/pets/data/PetProvider.java index f98a2a5d..78f23282 100644 --- a/app/src/main/java/com/example/android/pets/data/PetProvider.java +++ b/app/src/main/java/com/example/android/pets/data/PetProvider.java @@ -181,7 +181,69 @@ public int delete(Uri uri, String s, String[] strings) { } @Override - public int update(Uri uri, ContentValues contentValues, String s, String[] strings) { - return 0; + 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 = 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(PetEntry.COLUMN_PET_NAME)) { + String name = values.getAsString(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(PetEntry.COLUMN_PET_GENDER)) { + Integer gender = values.getAsInteger(PetEntry.COLUMN_PET_GENDER); + if (gender == null || !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(PetEntry.COLUMN_PET_WEIGHT)) { + // Check that the weight is greater than or equal to 0 kg + Integer weight = values.getAsInteger(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 = mDbHelper.getWritableDatabase(); + + // Returns the number of database rows affected by the update statement + return database.update(PetEntry.TABLE_NAME, values, selection, selectionArgs); } } From 81fe10a47364d2484770d95498595dd8f71d3b24 Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Wed, 20 Jul 2016 09:47:55 -0700 Subject: [PATCH 09/23] 1.15 Implement ContentProvider delete() method --- .../android/pets/data/PetProvider.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/example/android/pets/data/PetProvider.java b/app/src/main/java/com/example/android/pets/data/PetProvider.java index 78f23282..dd3c8935 100644 --- a/app/src/main/java/com/example/android/pets/data/PetProvider.java +++ b/app/src/main/java/com/example/android/pets/data/PetProvider.java @@ -175,11 +175,6 @@ private Uri insertPet(Uri uri, ContentValues values) { return ContentUris.withAppendedId(uri, id); } - @Override - public int delete(Uri uri, String s, String[] strings) { - return 0; - } - @Override public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) { @@ -246,4 +241,24 @@ private int updatePet(Uri uri, ContentValues values, String selection, String[] // Returns the number of database rows affected by the update statement return database.update(PetEntry.TABLE_NAME, values, selection, selectionArgs); } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + // Get writeable database + SQLiteDatabase database = mDbHelper.getWritableDatabase(); + + final int match = sUriMatcher.match(uri); + switch (match) { + case PETS: + // Delete all rows that match the selection and selection args + return database.delete(PetEntry.TABLE_NAME, selection, selectionArgs); + case PET_ID: + // Delete a single row given by the ID in the URI + selection = PetEntry._ID + "=?"; + selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) }; + return database.delete(PetEntry.TABLE_NAME, selection, selectionArgs); + default: + throw new IllegalArgumentException("Deletion is not supported for " + uri); + } + } } From a244b5a4cbda9c5fb7b551032b40166542da9869 Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Wed, 20 Jul 2016 10:21:29 -0700 Subject: [PATCH 10/23] 1.16 Implement ContentProvider getType() method --- .../example/android/pets/data/PetContract.java | 13 +++++++++++++ .../example/android/pets/data/PetProvider.java | 18 +++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/example/android/pets/data/PetContract.java b/app/src/main/java/com/example/android/pets/data/PetContract.java index a3f5a55c..e1de0978 100755 --- a/app/src/main/java/com/example/android/pets/data/PetContract.java +++ b/app/src/main/java/com/example/android/pets/data/PetContract.java @@ -16,6 +16,7 @@ package com.example.android.pets.data; import android.net.Uri; +import android.content.ContentResolver; import android.provider.BaseColumns; /** @@ -58,6 +59,18 @@ public static final class PetEntry implements BaseColumns { /** The content URI to access the pet data in the provider */ public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, PATH_PETS); + /** + * 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; + /** Name of database table for pets */ public final static String TABLE_NAME = "pets"; diff --git a/app/src/main/java/com/example/android/pets/data/PetProvider.java b/app/src/main/java/com/example/android/pets/data/PetProvider.java index dd3c8935..376d2826 100644 --- a/app/src/main/java/com/example/android/pets/data/PetProvider.java +++ b/app/src/main/java/com/example/android/pets/data/PetProvider.java @@ -119,11 +119,6 @@ public Cursor query(Uri uri, String[] projection, String selection, String[] sel return cursor; } - @Override - public String getType(Uri uri) { - return null; - } - @Override public Uri insert(Uri uri, ContentValues contentValues) { final int match = sUriMatcher.match(uri); @@ -261,4 +256,17 @@ public int delete(Uri uri, String selection, String[] selectionArgs) { throw new IllegalArgumentException("Deletion is not supported for " + uri); } } + + @Override + public String getType(Uri uri) { + final int match = sUriMatcher.match(uri); + switch (match) { + case PETS: + return PetEntry.CONTENT_LIST_TYPE; + case PET_ID: + return PetEntry.CONTENT_ITEM_TYPE; + default: + throw new IllegalStateException("Unknown URI " + uri + " with match " + match); + } + } } From feca5f24606aa1711362ff1d65ab85ffc94688ae Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Mon, 25 Jul 2016 13:29:04 -0700 Subject: [PATCH 11/23] 1.17 Create PetCursorAdapter to display list of pets in ListView --- .../example/android/pets/CatalogActivity.java | 51 ++--------- .../android/pets/PetCursorAdapter.java | 88 +++++++++++++++++++ app/src/main/res/layout/activity_catalog.xml | 7 +- app/src/main/res/layout/list_item.xml | 36 ++++++++ 4 files changed, 134 insertions(+), 48 deletions(-) create mode 100644 app/src/main/java/com/example/android/pets/PetCursorAdapter.java create mode 100644 app/src/main/res/layout/list_item.xml diff --git a/app/src/main/java/com/example/android/pets/CatalogActivity.java b/app/src/main/java/com/example/android/pets/CatalogActivity.java index b34e1828..728e4b34 100755 --- a/app/src/main/java/com/example/android/pets/CatalogActivity.java +++ b/app/src/main/java/com/example/android/pets/CatalogActivity.java @@ -25,7 +25,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.widget.TextView; +import android.widget.ListView; import com.example.android.pets.data.PetContract.PetEntry; @@ -79,51 +79,14 @@ private void displayDatabaseInfo() { null, // Selection criteria null); // The sort order for the returned rows - TextView displayView = (TextView) findViewById(R.id.text_view_pet); + // Find the ListView which will be populated with the pet data + ListView petListView = (ListView) findViewById(R.id.list); - try { - // Create a header in the Text View that looks like this: - // - // The pets table contains pets. - // _id - name - breed - gender - weight - // - // In the while loop below, iterate through the rows of the cursor and display - // the information from each column in this order. - displayView.setText("The pets table contains " + cursor.getCount() + " pets.\n\n"); - displayView.append(PetEntry._ID + " - " + - PetEntry.COLUMN_PET_NAME + " - " + - PetEntry.COLUMN_PET_BREED + " - " + - PetEntry.COLUMN_PET_GENDER + " - " + - PetEntry.COLUMN_PET_WEIGHT + "\n"); + // Setup an Adapter to create a list item for each row of pet data in the Cursor. + PetCursorAdapter adapter = new PetCursorAdapter(this, cursor); - // Figure out the index of each column - int idColumnIndex = cursor.getColumnIndex(PetEntry._ID); - 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); - - // Iterate through all the returned rows in the cursor - while (cursor.moveToNext()) { - // Use that index to extract the String or Int value of the word - // at the current row the cursor is on. - int currentID = cursor.getInt(idColumnIndex); - String currentName = cursor.getString(nameColumnIndex); - String currentBreed = cursor.getString(breedColumnIndex); - int currentGender = cursor.getInt(genderColumnIndex); - int currentWeight = cursor.getInt(weightColumnIndex); - // Display the values from each column of the current row in the cursor in the TextView - displayView.append(("\n" + currentID + " - " + - currentName + " - " + - currentBreed + " - " + - currentGender + " - " + - currentWeight)); - } - } finally { - // Always close the cursor when you're done reading from it. This releases all its - // resources and makes it invalid. - cursor.close(); - } + // Attach the adapter to the ListView. + petListView.setAdapter(adapter); } /** diff --git a/app/src/main/java/com/example/android/pets/PetCursorAdapter.java b/app/src/main/java/com/example/android/pets/PetCursorAdapter.java new file mode 100644 index 00000000..2811471f --- /dev/null +++ b/app/src/main/java/com/example/android/pets/PetCursorAdapter.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.pets; + +import android.content.Context; +import android.database.Cursor; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.TextView; + +import com.example.android.pets.data.PetContract.PetEntry; + +/** + * {@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) { + // Inflate a list item view using the layout specified in list_item.xml + 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) { + // Find individual views that we want to modify in the list item layout + TextView nameTextView = (TextView) view.findViewById(R.id.name); + TextView summaryTextView = (TextView) view.findViewById(R.id.summary); + + // 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); + } +} diff --git a/app/src/main/res/layout/activity_catalog.xml b/app/src/main/res/layout/activity_catalog.xml index 94f45e32..5e7c77a0 100755 --- a/app/src/main/res/layout/activity_catalog.xml +++ b/app/src/main/res/layout/activity_catalog.xml @@ -18,11 +18,10 @@ android:layout_height="match_parent" tools:context=".CatalogActivity"> - + android:layout_height="match_parent"/> + + + + + + + + \ No newline at end of file From 636e6bd21f25528a00f0629e18504eadc315bda7 Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Wed, 27 Jul 2016 10:31:25 -0700 Subject: [PATCH 12/23] 1.18 Add empty view to the ListView --- .../example/android/pets/CatalogActivity.java | 7 ++++ app/src/main/res/layout/activity_catalog.xml | 38 +++++++++++++++++++ app/src/main/res/values/strings.xml | 6 +++ 3 files changed, 51 insertions(+) diff --git a/app/src/main/java/com/example/android/pets/CatalogActivity.java b/app/src/main/java/com/example/android/pets/CatalogActivity.java index 728e4b34..7992ffbe 100755 --- a/app/src/main/java/com/example/android/pets/CatalogActivity.java +++ b/app/src/main/java/com/example/android/pets/CatalogActivity.java @@ -48,6 +48,13 @@ public void onClick(View view) { startActivity(intent); } }); + + // Find the ListView which will be populated with the pet data + ListView petListView = (ListView) findViewById(R.id.list); + + // 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); + petListView.setEmptyView(emptyView); } @Override diff --git a/app/src/main/res/layout/activity_catalog.xml b/app/src/main/res/layout/activity_catalog.xml index 5e7c77a0..94b0a93d 100755 --- a/app/src/main/res/layout/activity_catalog.xml +++ b/app/src/main/res/layout/activity_catalog.xml @@ -23,6 +23,44 @@ android:layout_width="match_parent" android:layout_height="match_parent"/> + + + + + + + + + + Delete All Pets + + It\'s a bit lonely here... + + + Get started by adding a pet + Add a Pet From dec30b11846978d51afc33430dad72a410142795 Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Wed, 27 Jul 2016 10:54:31 -0700 Subject: [PATCH 13/23] 1.19 Switch to CursorLoader --- .../example/android/pets/CatalogActivity.java | 83 ++++++++++--------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/com/example/android/pets/CatalogActivity.java b/app/src/main/java/com/example/android/pets/CatalogActivity.java index 7992ffbe..f4a95da2 100755 --- a/app/src/main/java/com/example/android/pets/CatalogActivity.java +++ b/app/src/main/java/com/example/android/pets/CatalogActivity.java @@ -15,8 +15,11 @@ */ package com.example.android.pets; +import android.app.LoaderManager; import android.content.ContentValues; +import android.content.CursorLoader; import android.content.Intent; +import android.content.Loader; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; @@ -32,7 +35,14 @@ /** * Displays list of pets that were entered and stored in the app. */ -public class CatalogActivity extends AppCompatActivity { +public class CatalogActivity extends AppCompatActivity implements + LoaderManager.LoaderCallbacks { + + /** Identifier for the pet data loader */ + private static final int PET_LOADER = 0; + + /** Adapter for the ListView */ + PetCursorAdapter mCursorAdapter; @Override protected void onCreate(Bundle savedInstanceState) { @@ -55,45 +65,14 @@ public void onClick(View 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); petListView.setEmptyView(emptyView); - } - - @Override - protected void onStart() { - super.onStart(); - displayDatabaseInfo(); - } - - /** - * Temporary helper method to display information in the onscreen TextView about the state of - * the pets database. - */ - private void displayDatabaseInfo() { - // Define a projection that specifies which columns from the database - // you will actually use after this query. - String[] projection = { - PetEntry._ID, - PetEntry.COLUMN_PET_NAME, - PetEntry.COLUMN_PET_BREED, - PetEntry.COLUMN_PET_GENDER, - PetEntry.COLUMN_PET_WEIGHT }; - - // Perform a query on the provider using the ContentResolver. - // Use the {@link PetEntry#CONTENT_URI} to access the pet data. - Cursor cursor = getContentResolver().query( - PetEntry.CONTENT_URI, // The content URI of the words table - projection, // The columns to return for each row - null, // Selection criteria - null, // Selection criteria - null); // The sort order for the returned rows - - // Find the ListView which will be populated with the pet data - ListView petListView = (ListView) findViewById(R.id.list); // Setup an Adapter to create a list item for each row of pet data in the Cursor. - PetCursorAdapter adapter = new PetCursorAdapter(this, cursor); + // There is no pet data yet (until the loader finishes) so pass in null for the Cursor. + mCursorAdapter = new PetCursorAdapter(this, null); + petListView.setAdapter(mCursorAdapter); - // Attach the adapter to the ListView. - petListView.setAdapter(adapter); + // Kick off the loader + getLoaderManager().initLoader(PET_LOADER, null, this); } /** @@ -130,7 +109,6 @@ public boolean onOptionsItemSelected(MenuItem item) { // Respond to a click on the "Insert dummy data" menu option case R.id.action_insert_dummy_data: insertPet(); - displayDatabaseInfo(); return true; // Respond to a click on the "Delete all entries" menu option case R.id.action_delete_all_entries: @@ -139,4 +117,33 @@ public boolean onOptionsItemSelected(MenuItem item) { } return super.onOptionsItemSelected(item); } + + @Override + public Loader onCreateLoader(int i, Bundle bundle) { + // Define a projection that specifies the columns from the table we care about. + String[] projection = { + PetEntry._ID, + PetEntry.COLUMN_PET_NAME, + PetEntry.COLUMN_PET_BREED }; + + // This loader will execute the ContentProvider's query method on a background thread + return new CursorLoader(this, // Parent activity context + PetEntry.CONTENT_URI, // Provider content URI to query + 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 data) { + // Update {@link PetCursorAdapter} with this new cursor containing updated pet data + mCursorAdapter.swapCursor(data); + } + + @Override + public void onLoaderReset(Loader loader) { + // Callback called when the data needs to be deleted + mCursorAdapter.swapCursor(null); + } } From e58777173ce9c1ddbdf54bf766eb3a12871865d8 Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Wed, 27 Jul 2016 17:50:54 -0700 Subject: [PATCH 14/23] 1.20 Modify ContentProvider so Loader refreshes data automatically --- .../android/pets/data/PetProvider.java | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/example/android/pets/data/PetProvider.java b/app/src/main/java/com/example/android/pets/data/PetProvider.java index 376d2826..3e75c0b7 100644 --- a/app/src/main/java/com/example/android/pets/data/PetProvider.java +++ b/app/src/main/java/com/example/android/pets/data/PetProvider.java @@ -116,6 +116,13 @@ public Cursor query(Uri uri, String[] projection, String selection, String[] sel default: throw new IllegalArgumentException("Cannot query unknown URI " + uri); } + + // Set notification URI on the Cursor, + // so we know what content URI the Cursor was created for. + // If the data at this URI changes, then we know we need to update the Cursor. + cursor.setNotificationUri(getContext().getContentResolver(), uri); + + // Return the cursor return cursor; } @@ -166,6 +173,9 @@ private Uri insertPet(Uri uri, ContentValues values) { return null; } + // Notify all listeners that the data has changed for the pet content URI + 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); } @@ -233,8 +243,17 @@ private int updatePet(Uri uri, ContentValues values, String selection, String[] // Otherwise, get writeable database to update the data SQLiteDatabase database = mDbHelper.getWritableDatabase(); - // Returns the number of database rows affected by the update statement - return database.update(PetEntry.TABLE_NAME, values, selection, selectionArgs); + // Perform the update on the database and get the number of rows affected + int rowsUpdated = database.update(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; } @Override @@ -242,19 +261,33 @@ public int delete(Uri uri, String selection, String[] selectionArgs) { // Get writeable database SQLiteDatabase database = mDbHelper.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 - return database.delete(PetEntry.TABLE_NAME, selection, selectionArgs); + rowsDeleted = database.delete(PetEntry.TABLE_NAME, selection, selectionArgs); + break; case PET_ID: // Delete a single row given by the ID in the URI selection = PetEntry._ID + "=?"; selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) }; - return database.delete(PetEntry.TABLE_NAME, selection, selectionArgs); + rowsDeleted = database.delete(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 From c0d42fb417295117a5f063bfdc3b8d4f03317703 Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Wed, 27 Jul 2016 18:45:51 -0700 Subject: [PATCH 15/23] 1.21 Clicking on list item opens EditorActivity --- app/src/main/AndroidManifest.xml | 1 - .../example/android/pets/CatalogActivity.java | 24 +++++++++++++++++++ .../example/android/pets/EditorActivity.java | 16 +++++++++++++ app/src/main/res/values/strings.xml | 3 +++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5ba2a60a..311a069c 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,7 +29,6 @@ diff --git a/app/src/main/java/com/example/android/pets/CatalogActivity.java b/app/src/main/java/com/example/android/pets/CatalogActivity.java index f4a95da2..839f4b7f 100755 --- a/app/src/main/java/com/example/android/pets/CatalogActivity.java +++ b/app/src/main/java/com/example/android/pets/CatalogActivity.java @@ -16,6 +16,7 @@ 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; @@ -28,6 +29,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.widget.AdapterView; import android.widget.ListView; import com.example.android.pets.data.PetContract.PetEntry; @@ -71,6 +73,28 @@ public void onClick(View view) { mCursorAdapter = new PetCursorAdapter(this, null); petListView.setAdapter(mCursorAdapter); + // Setup the item click listener + petListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int position, long id) { + // Create new intent to go to {@link EditorActivity} + Intent intent = new Intent(CatalogActivity.this, EditorActivity.class); + + // Form the content URI that represents the specific pet that was clicked on, + // by appending the "id" (passed as input to this method) onto the + // {@link PetEntry#CONTENT_URI}. + // For example, the URI would be "content://com.example.android.pets/pets/2" + // if the pet with ID 2 was clicked on. + Uri currentPetUri = ContentUris.withAppendedId(PetEntry.CONTENT_URI, id); + + // Set the URI on the data field of the intent + intent.setData(currentPetUri); + + // Launch the {@link EditorActivity} to display the data for the current pet. + startActivity(intent); + } + }); + // Kick off the loader getLoaderManager().initLoader(PET_LOADER, null, this); } diff --git a/app/src/main/java/com/example/android/pets/EditorActivity.java b/app/src/main/java/com/example/android/pets/EditorActivity.java index 0127757b..22d715e9 100755 --- a/app/src/main/java/com/example/android/pets/EditorActivity.java +++ b/app/src/main/java/com/example/android/pets/EditorActivity.java @@ -16,6 +16,7 @@ package com.example.android.pets; import android.content.ContentValues; +import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.NavUtils; @@ -61,6 +62,21 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_editor); + // Examine the intent that was used to launch this activity, + // in order to figure out if we're creating a new pet or editing an existing one. + Intent intent = getIntent(); + Uri currentPetUri = intent.getData(); + + // If the intent DOES NOT contain a pet content URI, then we know that we are + // creating a new pet. + if (currentPetUri == null) { + // This is a new pet, so change the app bar to say "Add a Pet" + setTitle(getString(R.string.editor_activity_title_new_pet)); + } else { + // Otherwise this is an existing pet, so change app bar to say "Edit Pet" + setTitle(getString(R.string.editor_activity_title_edit_pet)); + } + // 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); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 499aea6f..c903e863 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,9 @@ Add a Pet + + Edit Pet + Save From 8d4b875ecb71f938d135d4f2d8e19f420eab1135 Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Wed, 27 Jul 2016 19:24:36 -0700 Subject: [PATCH 16/23] 1.22 Load existing pet data from database --- .../example/android/pets/EditorActivity.java | 94 ++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/android/pets/EditorActivity.java b/app/src/main/java/com/example/android/pets/EditorActivity.java index 22d715e9..b8be4b16 100755 --- a/app/src/main/java/com/example/android/pets/EditorActivity.java +++ b/app/src/main/java/com/example/android/pets/EditorActivity.java @@ -15,8 +15,12 @@ */ package com.example.android.pets; +import android.app.LoaderManager; import android.content.ContentValues; +import android.content.CursorLoader; import android.content.Intent; +import android.content.Loader; +import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.NavUtils; @@ -36,7 +40,14 @@ /** * Allows user to create a new pet or edit an existing one. */ -public class EditorActivity extends AppCompatActivity { +public class EditorActivity extends AppCompatActivity implements + LoaderManager.LoaderCallbacks { + + /** Identifier for the pet data loader */ + private static final int EXISTING_PET_LOADER = 0; + + /** Content URI for the existing pet (null if it's a new pet) */ + private Uri mCurrentPetUri; /** EditText field to enter the pet's name */ private EditText mNameEditText; @@ -65,16 +76,20 @@ protected void onCreate(Bundle savedInstanceState) { // Examine the intent that was used to launch this activity, // in order to figure out if we're creating a new pet or editing an existing one. Intent intent = getIntent(); - Uri currentPetUri = intent.getData(); + mCurrentPetUri = intent.getData(); // If the intent DOES NOT contain a pet content URI, then we know that we are // creating a new pet. - if (currentPetUri == null) { + if (mCurrentPetUri == null) { // This is a new pet, so change the app bar to say "Add a Pet" setTitle(getString(R.string.editor_activity_title_new_pet)); } else { // Otherwise this is an existing pet, so change app bar to say "Edit Pet" setTitle(getString(R.string.editor_activity_title_edit_pet)); + + // Initialize a loader to read the pet data from the database + // and display the current values in the editor + getLoaderManager().initLoader(EXISTING_PET_LOADER, null, this); } // Find all relevant views that we will need to read user input from @@ -190,4 +205,77 @@ public boolean onOptionsItemSelected(MenuItem item) { } 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 + } } \ No newline at end of file From 20d0524a6c88eb1dcd1021c93ec59e5047508137 Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Mon, 1 Aug 2016 18:07:13 -0700 Subject: [PATCH 17/23] 1.23 Save changes to existing pet if it already exists --- .../example/android/pets/EditorActivity.java | 49 +++++++++++++------ app/src/main/res/values/strings.xml | 6 +++ 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/example/android/pets/EditorActivity.java b/app/src/main/java/com/example/android/pets/EditorActivity.java index b8be4b16..17370332 100755 --- a/app/src/main/java/com/example/android/pets/EditorActivity.java +++ b/app/src/main/java/com/example/android/pets/EditorActivity.java @@ -141,9 +141,9 @@ public void onNothingSelected(AdapterView parent) { } /** - * Get user input from editor and save new pet into database. + * Get user input from editor and save pet into database. */ - private void insertPet() { + private void savePet() { // Read from input fields // Use trim to eliminate leading or trailing white space String nameString = mNameEditText.getText().toString().trim(); @@ -159,18 +159,39 @@ private void insertPet() { values.put(PetEntry.COLUMN_PET_GENDER, mGender); values.put(PetEntry.COLUMN_PET_WEIGHT, weight); - // 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(); + // Determine if this is a new or existing pet by checking if mCurrentPetUri is null or not + if (mCurrentPetUri == null) { + // This is a NEW pet, so 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, the insertion was successful and we can display a toast. - Toast.makeText(this, getString(R.string.editor_insert_pet_successful), - Toast.LENGTH_SHORT).show(); + // 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(); + } } } @@ -189,7 +210,7 @@ public boolean onOptionsItemSelected(MenuItem item) { // Respond to a click on the "Save" menu option case R.id.action_save: // Save pet to database - insertPet(); + savePet(); // Exit activity finish(); return true; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c903e863..faf8e6c1 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,6 +44,12 @@ Error with saving pet + + Pet updated + + + Error with updating pet + Overview From 6a82f79557073a70713c03e726b6f192568a9d11 Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Mon, 1 Aug 2016 19:16:00 -0700 Subject: [PATCH 18/23] 1.24 Prevent crash with blank editor --- .../example/android/pets/EditorActivity.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/android/pets/EditorActivity.java b/app/src/main/java/com/example/android/pets/EditorActivity.java index 17370332..3b9d459b 100755 --- a/app/src/main/java/com/example/android/pets/EditorActivity.java +++ b/app/src/main/java/com/example/android/pets/EditorActivity.java @@ -149,7 +149,16 @@ private void savePet() { String nameString = mNameEditText.getText().toString().trim(); String breedString = mBreedEditText.getText().toString().trim(); String weightString = mWeightEditText.getText().toString().trim(); - int weight = Integer.parseInt(weightString); + + // Check if this is supposed to be a new pet + // and check if all the fields in the editor are blank + if (mCurrentPetUri == null && + TextUtils.isEmpty(nameString) && TextUtils.isEmpty(breedString) && + TextUtils.isEmpty(weightString) && mGender == PetEntry.GENDER_UNKNOWN) { + // Since no fields were modified, we can return early without creating a new pet. + // No need to create ContentValues and no need to do any ContentProvider operations. + return; + } // Create a ContentValues object where column names are the keys, // and pet attributes from the editor are the values. @@ -157,6 +166,12 @@ private void savePet() { 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); // Determine if this is a new or existing pet by checking if mCurrentPetUri is null or not From bea7d9080f06d447892c634f6271cb83eef9762b Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Tue, 2 Aug 2016 11:38:26 -0700 Subject: [PATCH 19/23] 1.25 Warn user about losing unsaved changes --- .../example/android/pets/EditorActivity.java | 104 +++++++++++++++++- app/src/main/res/values/strings.xml | 9 ++ 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/android/pets/EditorActivity.java b/app/src/main/java/com/example/android/pets/EditorActivity.java index 3b9d459b..e167da83 100755 --- a/app/src/main/java/com/example/android/pets/EditorActivity.java +++ b/app/src/main/java/com/example/android/pets/EditorActivity.java @@ -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; + } + }; + @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); + 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 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 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(); + } + } + }); + + // Create and show the AlertDialog + AlertDialog alertDialog = builder.create(); + alertDialog.show(); + } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index faf8e6c1..b1ea2576 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,6 +50,15 @@ Error with updating pet + + Discard your changes and quit editing? + + + Discard + + + Keep Editing + Overview From d90609a61d5b763c14f997962a38dcbfacb5efef Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Thu, 4 Aug 2016 11:53:54 -0700 Subject: [PATCH 20/23] 1.26 Hide delete menu option for new pets --- .../example/android/pets/EditorActivity.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/src/main/java/com/example/android/pets/EditorActivity.java b/app/src/main/java/com/example/android/pets/EditorActivity.java index e167da83..4ef6f0d3 100755 --- a/app/src/main/java/com/example/android/pets/EditorActivity.java +++ b/app/src/main/java/com/example/android/pets/EditorActivity.java @@ -101,6 +101,10 @@ protected void onCreate(Bundle savedInstanceState) { if (mCurrentPetUri == null) { // This is a new pet, so change the app bar to say "Add a Pet" setTitle(getString(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 { // Otherwise this is an existing pet, so change app bar to say "Edit Pet" setTitle(getString(R.string.editor_activity_title_edit_pet)); @@ -244,6 +248,21 @@ public boolean onCreateOptionsMenu(Menu menu) { return true; } + /** + * 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; + } + @Override public boolean onOptionsItemSelected(MenuItem item) { // User clicked on a menu option in the app bar overflow menu From c1a4418259c30c32bb2c8b0250cfee0240e8ac4b Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Tue, 2 Aug 2016 12:32:22 -0700 Subject: [PATCH 21/23] 1.27 Delete pet from editor menu item --- .../example/android/pets/EditorActivity.java | 59 ++++++++++++++++++- app/src/main/res/values/strings.xml | 15 +++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/android/pets/EditorActivity.java b/app/src/main/java/com/example/android/pets/EditorActivity.java index 4ef6f0d3..03f098f8 100755 --- a/app/src/main/java/com/example/android/pets/EditorActivity.java +++ b/app/src/main/java/com/example/android/pets/EditorActivity.java @@ -276,7 +276,8 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; // Respond to a click on the "Delete" menu option case R.id.action_delete: - // Do nothing for now + // 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: @@ -433,4 +434,60 @@ public void onClick(DialogInterface dialog, int id) { AlertDialog alertDialog = builder.create(); alertDialog.show(); } + + /** + * Prompt the user to confirm that they want to delete this pet. + */ + 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(); + } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b1ea2576..cfebeefb 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -59,6 +59,21 @@ Keep Editing + + Pet deleted + + + Error with deleting pet + + + Delete this pet? + + + Delete + + + Cancel + Overview From 311f80d83b9a36ad0267ae31deb15285b12c2645 Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Tue, 2 Aug 2016 15:04:41 -0700 Subject: [PATCH 22/23] 1.28 Delete all pets from catalog menu item --- .../com/example/android/pets/CatalogActivity.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/android/pets/CatalogActivity.java b/app/src/main/java/com/example/android/pets/CatalogActivity.java index 839f4b7f..78935997 100755 --- a/app/src/main/java/com/example/android/pets/CatalogActivity.java +++ b/app/src/main/java/com/example/android/pets/CatalogActivity.java @@ -26,6 +26,7 @@ import android.os.Bundle; 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; @@ -118,6 +119,14 @@ private void insertPet() { Uri newUri = getContentResolver().insert(PetEntry.CONTENT_URI, values); } + /** + * 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"); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu options from the res/menu/menu_catalog.xml file. @@ -136,7 +145,7 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; // Respond to a click on the "Delete all entries" menu option case R.id.action_delete_all_entries: - // Do nothing for now + deleteAllPets(); return true; } return super.onOptionsItemSelected(item); From d942c19b45cdce4e3d9afba8e2165c7eebc389fb Mon Sep 17 00:00:00 2001 From: Beginning Android Date: Thu, 4 Aug 2016 14:05:18 -0700 Subject: [PATCH 23/23] =?UTF-8?q?1.29=20Display=20=E2=80=9CUnknown=20breed?= =?UTF-8?q?=E2=80=9D=20for=20pets=20without=20breed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/android/pets/PetCursorAdapter.java | 7 +++++++ app/src/main/res/values/strings.xml | 3 +++ 2 files changed, 10 insertions(+) diff --git a/app/src/main/java/com/example/android/pets/PetCursorAdapter.java b/app/src/main/java/com/example/android/pets/PetCursorAdapter.java index 2811471f..a1ae1bca 100644 --- a/app/src/main/java/com/example/android/pets/PetCursorAdapter.java +++ b/app/src/main/java/com/example/android/pets/PetCursorAdapter.java @@ -17,6 +17,7 @@ import android.content.Context; import android.database.Cursor; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -81,6 +82,12 @@ public void bindView(View view, Context context, Cursor cursor) { String petName = cursor.getString(nameColumnIndex); String petBreed = cursor.getString(breedColumnIndex); + // 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(petBreed)) { + petBreed = context.getString(R.string.unknown_breed); + } + // Update the TextViews with the attributes for the current pet nameTextView.setText(petName); summaryTextView.setText(petBreed); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cfebeefb..b2540136 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -103,4 +103,7 @@ Female + + + Unknown breed