Skip to content

Commit

Permalink
S09.01-Solution-ContentProviderFoundation
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Lustig authored and Jeremy Silver committed Jan 19, 2017
1 parent a12edc1 commit c607206
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 13 deletions.
7 changes: 6 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@
<!--The manifest entry for our SettingsActivity. Each Activity requires a manifest entry-->
<activity android:name=".SettingsActivity"/>

<!-- TODO (4) Register WeatherProvider in the manifest with the correct authorities -->
<!-- COMPLETED (6) Register WeatherProvider in the manifest with the correct authorities -->
<!-- Our ContentProvider -->
<provider
android:name=".data.WeatherProvider"
android:authorities="@string/content_authority"
android:exported="false"/>

</application>
</manifest>
198 changes: 186 additions & 12 deletions app/src/main/java/com/example/android/sunshine/data/WeatherProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import android.annotation.TargetApi;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
Expand All @@ -34,21 +35,100 @@
*/
public class WeatherProvider extends ContentProvider {

// TODO (5) Create static constant integer values named CODE_WEATHER & CODE_WEATHER_WITH_DATE to identify the URIs this ContentProvider can handle
// COMPLETED (5) Create static constant integer values named CODE_WEATHER & CODE_WEATHER_WITH_DATE to identify the URIs this ContentProvider can handle
/*
* These constant will be used to match URIs with the data they are looking for. We will take
* advantage of the UriMatcher class to make that matching MUCH easier than doing something
* ourselves, such as using regular expressions.
*/
public static final int CODE_WEATHER = 100;
public static final int CODE_WEATHER_WITH_DATE = 101;

// COMPLETED (8) Instantiate a static UriMatcher using the buildUriMatcher method
/*
* The URI Matcher used by this content provider. The leading "s" in this variable name
* signifies that this UriMatcher is a static member variable of WeatherProvider and is a
* common convention in Android programming.
*/
private static final UriMatcher sUriMatcher = buildUriMatcher();

// COMPLETED (1) Declare, but don't instantiate a WeatherDbHelper object called mOpenHelper
private WeatherDbHelper mOpenHelper;

// TODO (7) Instantiate a static UriMatcher using the buildUriMatcher method
// COMPLETED (7) Write a method called buildUriMatcher where you match URI's to their numeric ID
/**
* Creates the UriMatcher that will match each URI to the CODE_WEATHER and
* CODE_WEATHER_WITH_DATE constants defined above.
* <p>
* It's possible you might be thinking, "Why create a UriMatcher when you can use regular
* expressions instead? After all, we really just need to match some patterns, and we can
* use regular expressions to do that right?" Because you're not crazy, that's why.
* <p>
* UriMatcher does all the hard work for you. You just have to tell it which code to match
* with which URI, and it does the rest automagically. Remember, the best programmers try
* to never reinvent the wheel. If there is a solution for a problem that exists and has
* been tested and proven, you should almost always use it unless there is a compelling
* reason not to.
*
* @return A UriMatcher that correctly matches the constants for CODE_WEATHER and CODE_WEATHER_WITH_DATE
*/
public static UriMatcher buildUriMatcher() {

WeatherDbHelper mOpenHelper;
/*
* All paths added to the UriMatcher have a corresponding code to return when a match is
* found. The code passed into the constructor of UriMatcher here represents the code to
* return for the root URI. It's common to use NO_MATCH as the code for this case.
*/
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
final String authority = WeatherContract.CONTENT_AUTHORITY;

// TODO (6) Write a method called buildUriMatcher where you match URI's to their numeric ID
/*
* For each type of URI you want to add, create a corresponding code. Preferably, these are
* constant fields in your class so that you can use them throughout the class and you no
* they aren't going to change. In Sunshine, we use CODE_WEATHER or CODE_WEATHER_WITH_DATE.
*/

// TODO (1) Implement onCreate
/* This URI is content://com.example.android.sunshine/weather/ */
matcher.addURI(authority, WeatherContract.PATH_WEATHER, CODE_WEATHER);

/*
* This URI would look something like content://com.example.android.sunshine/weather/1472214172
* The "/#" signifies to the UriMatcher that if PATH_WEATHER is followed by ANY number,
* that it should return the CODE_WEATHER_WITH_DATE code
*/
matcher.addURI(authority, WeatherContract.PATH_WEATHER + "/#", CODE_WEATHER_WITH_DATE);

return matcher;
}

// COMPLETED (2) Override onCreate
/**
* In onCreate, we initialize our content provider on startup. This method is called for all
* registered content providers on the application main thread at application launch time.
* It must not perform lengthy operations, or application startup will be delayed.
*
* Nontrivial initialization (such as opening, upgrading, and scanning
* databases) should be deferred until the content provider is used (via {@link #query},
* {@link #bulkInsert(Uri, ContentValues[])}, etc).
*
* Deferred initialization keeps application startup fast, avoids unnecessary work if the
* provider turns out not to be needed, and stops database errors (such as a full disk) from
* halting application launch.
*
* @return true if the provider was successfully loaded, false otherwise
*/
@Override
public boolean onCreate() {
// TODO (2) Within onCreate, instantiate our mOpenHelper
/*
* As noted in the comment above, onCreate is run on the main thread, so performing any
* lengthy operations will cause lag in your app. Since WeatherDbHelper's constructor is
* very lightweight, we are safe to perform that initialization here.
*/
// COMPLETED (3) Within onCreate, instantiate our mOpenHelper
mOpenHelper = new WeatherDbHelper(getContext());

// TODO (3) Return true from onCreate to signify success performing setup
return false;
// COMPLETED (4) Return true from onCreate to signify success performing setup
return true;
}

/**
Expand All @@ -69,7 +149,7 @@ public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
throw new RuntimeException("Student, you need to implement the bulkInsert mehtod!");
}

// TODO (8) Provide an implementation for the query method
// COMPLETED (9) Provide an implementation for the query method
/**
* Handles query requests from clients. We will use this method in Sunshine to query for all
* of our weather data as well as to query for the weather on a particular day.
Expand All @@ -88,11 +168,105 @@ public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
throw new RuntimeException("Student, implement the query method!");

// TODO (9) Handle queries on both the weather and weather with date URI
Cursor cursor;

// COMPLETED (10) Handle queries on both the weather and weather with date URI
/*
* Here's the switch statement that, given a URI, will determine what kind of request is
* being made and query the database accordingly.
*/
switch (sUriMatcher.match(uri)) {

/*
* When sUriMatcher's match method is called with a URI that looks something like this
*
* content://com.example.android.sunshine/weather/1472214172
*
* sUriMatcher's match method will return the code that indicates to us that we need
* to return the weather for a particular date. The date in this code is encoded in
* milliseconds and is at the very end of the URI (1472214172) and can be accessed
* programmatically using Uri's getLastPathSegment method.
*
* In this case, we want to return a cursor that contains one row of weather data for
* a particular date.
*/
case CODE_WEATHER_WITH_DATE: {

/*
* In order to determine the date associated with this URI, we look at the last
* path segment. In the comment above, the last path segment is 1472214172 and
* represents the number of seconds since the epoch, or UTC time.
*/
String normalizedUtcDateString = uri.getLastPathSegment();

/*
* The query method accepts a string array of arguments, as there may be more
* than one "?" in the selection statement. Even though in our case, we only have
* one "?", we have to create a string array that only contains one element
* because this method signature accepts a string array.
*/
String[] selectionArguments = new String[]{normalizedUtcDateString};

cursor = mOpenHelper.getReadableDatabase().query(
/* Table we are going to query */
WeatherContract.WeatherEntry.TABLE_NAME,
/*
* A projection designates the columns we want returned in our Cursor.
* Passing null will return all columns of data within the Cursor.
* However, if you don't need all the data from the table, it's best
* practice to limit the columns returned in the Cursor with a projection.
*/
projection,
/*
* The URI that matches CODE_WEATHER_WITH_DATE contains a date at the end
* of it. We extract that date and use it with these next two lines to
* specify the row of weather we want returned in the cursor. We use a
* question mark here and then designate selectionArguments as the next
* argument for performance reasons. Whatever Strings are contained
* within the selectionArguments array will be inserted into the
* selection statement by SQLite under the hood.
*/
WeatherContract.WeatherEntry.COLUMN_DATE + " = ? ",
selectionArguments,
null,
null,
sortOrder);

break;
}

/*
* When sUriMatcher's match method is called with a URI that looks EXACTLY like this
*
* content://com.example.android.sunshine/weather/
*
* sUriMatcher's match method will return the code that indicates to us that we need
* to return all of the weather in our weather table.
*
* In this case, we want to return a cursor that contains every row of weather data
* in our weather table.
*/
case CODE_WEATHER: {
cursor = mOpenHelper.getReadableDatabase().query(
WeatherContract.WeatherEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);

break;
}

default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}

// TODO (10) Call setNotificationUri on the cursor and then return the cursor
// COMPLETED (11) Call setNotificationUri on the cursor and then return the cursor
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}

/**
Expand Down

0 comments on commit c607206

Please sign in to comment.