/* COMPLETED (1) Add content provider constants to the Contract Clients need to know how to access the task data, and it's your job to provide these content URI's for the path to that data: 1) Content authority, 2) Base content URI, 3) Path(s) to the tasks directory 4) Content URI for data in the TaskEntry class */
// The authority, which is how your code knows which Content Provider to access publicstaticfinalStringAUTHORITY="com.example.android.todolist";
// The base content URI = "content://" + <authority> publicstaticfinalUriBASE_CONTENT_URI= Uri.parse("content://" + AUTHORITY);
// Define the possible paths for accessing data in this contract // This is the path for the "tasks" directory publicstaticfinalStringPATH_TASKS="tasks";
/* TaskEntry is an inner class that defines the contents of the task table */ publicstaticfinalclassTaskEntryimplementsBaseColumns {
// TaskEntry content URI = base content URI + path publicstaticfinalUriCONTENT_URI= BASE_CONTENT_URI.buildUpon().appendPath(PATH_TASKS).build();
// Verify that TaskContentProvider extends from ContentProvider and implements required methods publicclassTaskContentProviderextendsContentProvider {
// COMPLETED (1) Define final integer constants for the directory of tasks and a single item. // It's convention to use 100, 200, 300, etc for directories, // and related ints (101, 102, ..) for items in that directory. publicstaticfinalintTASKS=100; publicstaticfinalintTASK_WITH_ID=101;
// COMPLETED (3) Declare a static variable for the Uri matcher that you construct privatestaticfinalUriMatchersUriMatcher= buildUriMatcher();
// COMPLETED (2) Define a static buildUriMatcher method that associates URI's with their int match /** Initialize a new matcher object without any matches, then use .addURI(String authority, String path, int match) to add matches */ publicstatic UriMatcher buildUriMatcher() {
// Initialize a UriMatcher with no matches by passing in NO_MATCH to the constructor UriMatcheruriMatcher=newUriMatcher(UriMatcher.NO_MATCH);
/* All paths added to the UriMatcher have a corresponding int. For each kind of uri you may want to access, add the corresponding match with addURI. The two calls below add matches for the task directory and a single item by ID. */ uriMatcher.addURI(TaskContract.AUTHORITY, TaskContract.PATH_TASKS, TASKS); uriMatcher.addURI(TaskContract.AUTHORITY, TaskContract.PATH_TASKS + "/#", TASK_WITH_ID);
return uriMatcher; }
// Member variable for a TaskDbHelper that's initialized in the onCreate() method private TaskDbHelper mTaskDbHelper;
/* onCreate() is where you should initialize anything you’ll need to setup your underlying data source. In this case, you’re working with a SQLite database, so you’ll need to initialize a DbHelper to gain access to it. */ @Override publicbooleanonCreate() { // Complete onCreate() and initialize a TaskDbhelper on startup // [Hint] Declare the DbHelper as a global variable
// Implement insert to handle requests to insert a single new row of data @Override public Uri insert(@NonNull Uri uri, ContentValues values) { // Get access to the task database (to write new data to) finalSQLiteDatabasedb= mTaskDbHelper.getWritableDatabase();
// Write URI matching code to identify the match for the tasks directory intmatch= sUriMatcher.match(uri); Uri returnUri; // URI to be returned
switch (match) { case TASKS: // Insert new values into the database // Inserting values into tasks table longid= db.insert(TABLE_NAME, null, values); if ( id > 0 ) { returnUri = ContentUris.withAppendedId(TaskContract.TaskEntry.CONTENT_URI, id); } else { thrownewandroid.database.SQLException("Failed to insert row into " + uri); } break; // Set the value for the returnedUri and write the default case for unknown URI's // Default case throws an UnsupportedOperationException default: thrownewUnsupportedOperationException("Unknown uri: " + uri); }
// Notify the resolver if the uri has been changed, and return the newly inserted URI getContext().getContentResolver().notifyChange(uri, null);
// Return constructed uri (this points to the newly inserted row of data) return returnUri; }
// Implement query to handle requests for data by URI @Override public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// Get access to underlying database (read-only for query) finalSQLiteDatabasedb= mTaskDbHelper.getReadableDatabase();
// Write URI match code // Write a query for the tasks directory and default case
switch (match) { // Query for the tasks directory case TASKS: retCursor = db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder); break;
// Add a case to query for a single row of data by ID // Use selections and selectionArgs to filter for that ID case TASK_WITH_ID: // Get the id from the URI Stringid= uri.getPathSegments().get(1);
// Selection is the _ID column = ?, and the Selection args = the row ID from the URI StringmSelection="_id=?"; String[] mSelectionArgs = newString[]{id};
// Construct a query as you would normally, passing in the selection/args retCursor = db.query(TABLE_NAME, projection, mSelection, mSelectionArgs, null, null, sortOrder); break;
// Update won't be used in the final ToDoList app but is implemented here for completeness // This updates a single item (by it's ID) in the tasks directory @Override publicintupdate(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
//Keep track of if an update occurs int tasksUpdated;
// match code intmatch= sUriMatcher.match(uri);
switch (match) { case TASK_WITH_ID: //update a single task by getting the id Stringid= uri.getPathSegments().get(1); //using selections tasksUpdated = mTaskDbHelper.getWritableDatabase().update(TABLE_NAME, values, "_id=?", newString[]{id}); break; default: thrownewUnsupportedOperationException("Unknown uri: " + uri); }
if (tasksUpdated != 0) { //set notifications if a task was updated getContext().getContentResolver().notifyChange(uri, null); }
// return number of tasks updated return tasksUpdated; }
// Implement delete to delete a single row of data @Override publicintdelete(@NonNull Uri uri, String selection, String[] selectionArgs) {
// Get access to the database and write URI matching code to recognize a single item finalSQLiteDatabasedb= mTaskDbHelper.getWritableDatabase();
intmatch= sUriMatcher.match(uri); // Keep track of the number of deleted tasks int tasksDeleted; // starts as 0
// Write the code to delete a single row of data // [Hint] Use selections to delete an item by its row ID switch (match) { // Handle the single item case, recognized by the ID included in the URI path case TASK_WITH_ID: // Get the task ID from the URI path Stringid= uri.getPathSegments().get(1); // Use selections/selectionArgs to filter for this ID tasksDeleted = db.delete(TABLE_NAME, "_id=?", newString[]{id}); break; default: thrownewUnsupportedOperationException("Unknown uri: " + uri); }
// Notify the resolver of a change and return the number of items deleted if (tasksDeleted != 0) { // A task was deleted, set notification getContext().getContentResolver().notifyChange(uri, null); }
// Return the number of tasks deleted return tasksDeleted; }
/* getType() handles requests for the MIME type of data We are working with two types of data: 1) a directory and 2) a single row of data. This method will not be used in our app, but gives a way to standardize the data formats that your provider accesses, and this can be useful for data organization. For now, this method will not be used but will be provided for completeness. */ @Override public String getType(@NonNull Uri uri) { intmatch= sUriMatcher.match(uri);
switch (match) { case TASKS: // directory return"vnd.android.cursor.dir" + "/" + TaskContract.AUTHORITY + "/" + TaskContract.PATH_TASKS; case TASK_WITH_ID: // single item type return"vnd.android.cursor.item" + "/" + TaskContract.AUTHORITY + "/" + TaskContract.PATH_TASKS; default: thrownewUnsupportedOperationException("Unknown uri: " + uri); } }
// COMPLETED (1) Create an inner class named WaitlistEntry class that implements the BaseColumns interface publicstaticfinalclassWaitlistEntryimplementsBaseColumns { // COMPLETED (2) Inside create a static final members for the table name and each of the db columns publicstaticfinalStringTABLE_NAME="waitlist"; publicstaticfinalStringCOLUMN_GUEST_NAME="guestName"; publicstaticfinalStringCOLUMN_PARTY_SIZE="partySize"; publicstaticfinalStringCOLUMN_TIMESTAMP="timestamp"; }
/* * This tag will be used for logging. It is best practice to use the class's name using * getSimpleName as that will greatly help to identify the location from which your logs are * being posted. */ privatestaticfinalStringTAG= MainActivity.class.getSimpleName();
/* Constant values for the names of each respective lifecycle callback */ privatestaticfinalStringON_CREATE="onCreate"; privatestaticfinalStringON_START="onStart"; privatestaticfinalStringON_RESUME="onResume"; privatestaticfinalStringON_PAUSE="onPause"; privatestaticfinalStringON_STOP="onStop"; privatestaticfinalStringON_RESTART="onRestart"; privatestaticfinalStringON_DESTROY="onDestroy"; privatestaticfinalStringON_SAVE_INSTANCE_STATE="onSaveInstanceState";
/* * This TextView will contain a running log of every lifecycle callback method called from this * Activity. This TextView can be reset to its default state by clicking the Button labeled * "Reset Log" */ private TextView mLifecycleDisplay;
/** * Called when the activity is first created. This is where you should do all of your normal * static set up: create views, bind data to lists, etc. * * Always followed by onStart(). * * @param savedInstanceState The Activity's previously frozen state, if there was one. */ @Override protectedvoidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
// COMPLETED (1) Use logAndAppend within onCreate logAndAppend(ON_CREATE); }
// COMPLETED (2) Override onStart, call super.onStart, and call logAndAppend with ON_START /** * Called when the activity is becoming visible to the user. * * Followed by onResume() if the activity comes to the foreground, or onStop() if it becomes * hidden. */ @Override protectedvoidonStart() { super.onStart();
logAndAppend(ON_START); }
// COMPLETED (3) Override onResume, call super.onResume, and call logAndAppend with ON_RESUME /** * Called when the activity will start interacting with the user. At this point your activity * is at the top of the activity stack, with user input going to it. * * Always followed by onPause(). */ @Override protectedvoidonResume() { super.onResume();
logAndAppend(ON_RESUME); }
// COMPLETED (4) Override onPause, call super.onPause, and call logAndAppend with ON_PAUSE /** * Called when the system is about to start resuming a previous activity. This is typically * used to commit unsaved changes to persistent data, stop animations and other things that may * be consuming CPU, etc. Implementations of this method must be very quick because the next * activity will not be resumed until this method returns. * * Followed by either onResume() if the activity returns back to the front, or onStop() if it * becomes invisible to the user. */ @Override protectedvoidonPause() { super.onPause();
logAndAppend(ON_PAUSE); }
// COMPLETED (5) Override onStop, call super.onStop, and call logAndAppend with ON_STOP /** * Called when the activity is no longer visible to the user, because another activity has been * resumed and is covering this one. This may happen either because a new activity is being * started, an existing one is being brought in front of this one, or this one is being * destroyed. * * Followed by either onRestart() if this activity is coming back to interact with the user, or * onDestroy() if this activity is going away. */ @Override protectedvoidonStop() { super.onStop();
logAndAppend(ON_STOP); }
// COMPLETED (6) Override onRestart, call super.onRestart, and call logAndAppend with ON_RESTART /** * Called after your activity has been stopped, prior to it being started again. * * Always followed by onStart() */ @Override protectedvoidonRestart() { super.onRestart();
logAndAppend(ON_RESTART); }
// COMPLETED (7) Override onDestroy, call super.onDestroy, and call logAndAppend with ON_DESTROY /** * The final call you receive before your activity is destroyed. This can happen either because * the activity is finishing (someone called finish() on it, or because the system is * temporarily destroying this instance of the activity to save space. You can distinguish * between these two scenarios with the isFinishing() method. */ @Override protectedvoidonDestroy() { super.onDestroy();
logAndAppend(ON_DESTROY); }
/** * Logs to the console and appends the lifecycle method name to the TextView so that you can * view the series of method callbacks that are called both from the app and from within * Android Studio's Logcat. * * @param lifecycleEvent The name of the event to be logged. */ privatevoidlogAndAppend(String lifecycleEvent) { Log.d(TAG, "Lifecycle Event: " + lifecycleEvent);
/** * This method resets the contents of the TextView to its default text of "Lifecycle callbacks" * * @param view The View that was clicked. In this case, it is the Button from our layout. */ publicvoidresetLifecycleDisplay(View view) { mLifecycleDisplay.setText("Lifecycle callbacks:\n"); } }
分布式版本控制系统(Distributed Version Control System,简称 DVCS)面世了,客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。因为每一次的提取操作,实际上都是一次对代码仓库的完整备份。更进一步,许多这类系统都可以指定和若干不同的远端代码仓库进行交互。籍此,你就可以在同一个项目中,分别和不同工作小组的人相互协作。你可以根据需要设定不同的协作流程,比如层次模型式的工作流,而这在以前的集中式系统中是无法实现的。