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/CatalogActivity.java b/app/src/main/java/com/example/android/pets/CatalogActivity.java
index 87d8a38c..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
@@ -69,9 +61,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 +70,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);
@@ -142,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();
@@ -153,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/PetContract.java b/app/src/main/java/com/example/android/pets/data/PetContract.java
index 8fe4990d..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
@@ -15,6 +15,8 @@
*/
package com.example.android.pets.data;
+import android.net.Uri;
+import android.content.ContentResolver;
import android.provider.BaseColumns;
/**
@@ -26,12 +28,49 @@ 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);
+
+ /**
+ * 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";
@@ -79,6 +118,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
new file mode 100644
index 00000000..376d2826
--- /dev/null
+++ b/app/src/main/java/com/example/android/pets/data/PetProvider.java
@@ -0,0 +1,272 @@
+/*
+ * 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.ContentUris;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+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;
+
+/**
+ * {@link ContentProvider} for Pets app.
+ */
+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;
+
+ /** 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;
+
+ @Override
+ public boolean onCreate() {
+ mDbHelper = new PetDbHelper(getContext());
+ return true;
+ }
+
+ @Override
+ 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
+ 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(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();
+
+ // 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
+ 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);
+ }
+
+ @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);
+ }
+ }
+
+ @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);
+ }
+ }
+}
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