====== Chapter 12 ====== This is just some text to get the section below to clear the Table of Contents. Otherwise the whole code block would appear very narrow. Kludgey for sure, but it gets the job done. \\ \\ \\ \\ \\ ===== p. 493: Code block ===== package com.hfad.starbuzz; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; public class DrinkActivity extends AppCompatActivity { public static final String EXTRA_DRINKNO = "drinkNo"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink); int drinkNo = (Integer)getIntent().getExtras().get(EXTRA_DRINKNO); //Create a cursor and populate Views from the cursor's data. try { // Gain access to our app's database: SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); // Get the name, description, and image for drinkNo: Cursor cursor = db.query ("DRINK", new String[] {"NAME", "DESCRIPTION", "IMAGE_RESOURCE_ID"}, "_id = ?", new String[] {Integer.toString(drinkNo)}, null, null,null); // Move to the first record in the Cursor if (cursor.moveToFirst()) { // Get the drink details from the cursor String nameText = cursor.getString(0); String descriptionText = cursor.getString(1); int photoId = cursor.getInt(2); // Populate the drink name TextView name = (TextView)findViewById(R.id.name); name.setText(nameText); // Populate the drink description TextView description = (TextView)findViewById(R.id.description); description.setText(descriptionText); // Populate the drink image ImageView photo = (ImageView)findViewById(R.id.photo); photo.setImageResource(photoId); photo.setContentDescription(nameText); } // Close up shop. cursor.close(); db.close(); } catch (SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); toast.show(); } } } ===== p. 504-505: Code block ===== Note the guards around objects I added in ''onDestroy()''. package com.hfad.starbuzz; import android.app.ListActivity; import android.content.Intent; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.ArrayAdapter; import android.widget.CursorAdapter; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.Toast; public class DrinkCategoryActivity extends ListActivity { private SQLiteDatabase db; // the app's db private Cursor cursor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ListView listDrinks = getListView(); // i.e., this.getListView(); // get the ListView used by // this ListActivity // Use a cursor via an adapter to populate listDrinks try { // Gain access to our app's database: SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); db = starbuzzDatabaseHelper.getReadableDatabase(); // Set a cursor on the _id and name: Cursor cursor = db.query ("DRINK", new String[] {"_id", "NAME"}, null, null, null, null,null); // Create a cursor adapter for the cursor CursorAdapter listAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, cursor, new String[]{"NAME"}, new int[]{android.R.id.text1}, 0); // Set listDrinks' adapter: listDrinks.setAdapter(listAdapter); } catch (SQLiteException e) { Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT).show(); } } @Override protected void onDestroy() { super.onDestroy(); if (cursor != null) {cursor.close();} if (db != null) {db.close();} } // Start new activity when an item is clicked @Override protected void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); Intent intent = new Intent(DrinkCategoryActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKNO, (int) id); startActivity(intent); } } ------------------------------- ===== p. 513-514: Code block ===== package com.hfad.starbuzz; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; public class DrinkActivity extends AppCompatActivity { public static final String EXTRA_DRINKNO = "drinkNo"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink); int drinkNo = (Integer)getIntent().getExtras().get(EXTRA_DRINKNO); //Create a cursor and populate Views from the cursor's data. try { // Gain access to our app's database: SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase(); // Get the name, description, and image for drinkNo: Cursor cursor = db.query ("DRINK", new String[] {"NAME", "DESCRIPTION", "IMAGE_RESOURCE_ID", "FAVORITE"}, "_id = ?", new String[] {Integer.toString(drinkNo)}, null, null,null); // Move to the first record in the Cursor if (cursor.moveToFirst()) { // Get the drink details from the cursor String nameText = cursor.getString(0); String descriptionText = cursor.getString(1); int photoId = cursor.getInt(2); boolean isFavorite = (cursor.getInt(3) == 1); // Populate the drink name TextView name = (TextView)findViewById(R.id.name); name.setText(nameText); // Populate the drink description TextView description = (TextView)findViewById(R.id.description); description.setText(descriptionText); // Populate the drink image ImageView photo = (ImageView)findViewById(R.id.photo); photo.setImageResource(photoId); photo.setContentDescription(nameText); // Populate the favorite checkbox CheckBox favorite = (CheckBox)findViewById(R.id.favorite); favorite.setChecked(isFavorite); } // Close up shop. cursor.close(); db.close(); } catch (SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); toast.show(); } } // Update the DB's FAVORITES field for this drink. public void onFavoriteClicked(View view) { int drinkNo = (Integer)getIntent().getExtras().get("drinkNo"); CheckBox favorite = (CheckBox)findViewById(R.id.favorite); ContentValues drinkValues = new ContentValues(); // Stage new FAVORITE value drinkValues.put("FAVORITE", favorite.isChecked()); SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(DrinkActivity.this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase(); db.update("DRINK", drinkValues, "_id = ?", new String[] {Integer.toString(drinkNo)}); db.close(); } catch(SQLiteException e) { Toast.makeText(DrinkActivity.this, "Database unavailable", Toast.LENGTH_SHORT).show(); } } } ===== p. 518-520: Code block ===== package com.hfad.starbuzz; import android.content.Intent; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.Gravity; import android.view.View; import android.widget.AdapterView; import android.widget.CursorAdapter; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.Toast; public class TopLevelActivity extends AppCompatActivity { private SQLiteDatabase db; private Cursor favoritesCursor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_top_level); AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener() { @Override public void onItemClick (AdapterView parent, View view, int position, long id) { if (position == 0) { Intent intent = new Intent(TopLevelActivity.this, DrinkCategoryActivity.class); startActivity(intent); } } }; // Set the top-level ListView's item click listener to OnItemClickListener. ListView listView = (ListView) findViewById(R.id.list_options); listView.setOnItemClickListener(itemClickListener); // Populate the list_favorites ListView from a cursor. ListView listFavrotites = (ListView)findViewById(R.id.list_favorites); try { SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); db = starbuzzDatabaseHelper.getReadableDatabase(); // Get the data into a cursor favoritesCursor = db.query("DRINK", new String[]{"_id", "NAME"}, "FAVORITE = 1", null, null, null, null); // Map data to list via adapter CursorAdapter favoriteAdapter = new SimpleCursorAdapter( TopLevelActivity.this, android.R.layout.simple_list_item_1, favoritesCursor, new String[] {"NAME"}, new int[] {android.R.id.text1}, 0); listFavrotites.setAdapter(favoriteAdapter); // Handle click events on list items - show favorite item's activity listFavrotites.setOnItemClickListener( new AdapterView.OnItemClickListener() { @Override public void onItemClick( AdapterView parent, View view, int position, long id) { Intent intent = new Intent(TopLevelActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKNO, (int)id); startActivity(intent); } } ); } catch(SQLiteException e) { Toast.makeText(TopLevelActivity.this, "Database unavailable", Toast.LENGTH_SHORT).show(); } } @Override protected void onDestroy() { super.onDestroy(); if (favoritesCursor != null) {favoritesCursor.close();} if (db != null) {db.close();} } } ===== p. 524: Code block ===== @Override protected void onRestart() { super.onRestart(); try{ StarbuzzDatabaseHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); db = starbuzzDatabaseHelper.getReadableDatabase(); // Generate a new Cursor with the updated DB state Cursor newCursor = db.query("DRINK", new String[] { "_id", "NAME"}, "FAVORITE = 1", null, null, null, null); ListView listFavorites = (ListView)findViewById(R.id.list_favorites); CursorAdapter adapter = (CursorAdapter) listFavorites.getAdapter(); // change only the Cursor in the Adapter adapter.changeCursor(newCursor); favoritesCursor = newCursor; } catch(SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); toast.show(); } } ===== pp. 526+: Threads ===== Don't neglect the material on Android threads! Threading in Android is important enough that it should be in a section all its own. In addition to "user threads" (a.k.a., AsycTasks) being used with databases, they are also frequently used for accessing network resources so that your app's UI doesn't grind to a halt waiting for the resource to respond. From a programmer's point of view, Android is by default a single-thread environment: the "main thread" is where your code runs unless you tell Android different. The code is run //synchronously//, meaning that the next thing in your code won't be executed until the previous thing completes. So if you tell your app to do something that takes a long time, your app will lock up until that completes. The idea of an AsycTask is that you can start a process that run in the background (in a different thread) and then notify the main thread when it's done so it can respond to the new state of things. This leaves the main thread available to, say, update the UI and handle user events while the process, say, waits for Flickr to respond to an API request. All that downtime the app would have spent waiting for the network to do something is instead available for user interaction. In creating subclasses of AsycTask, you must override the ''doInBackground'' method (it's abstract). Overriding any of the other methods--''onPreExecute'', ''onProgressUpdate'', and ''onPostExecute''-- is optional. These methods let you use AyncTasks to encapsulate general tasks, not just the parts of a task that run in the background. ===== p. 530 ===== The UpdateDrinkTask discussed here will be added as an inner class to DrinkActivity later on. So don't create a new ''UpdateDrinkTask.java'' file, ok? ===== pp. 537-538 ===== I've added some Toasts to mark when some things are happening. You wouldn't want to do this in production. package com.hfad.starbuzz; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.os.AsyncTask; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; public class DrinkActivity extends AppCompatActivity { public static final String EXTRA_DRINKNO = "drinkNo"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink); int drinkNo = (Integer)getIntent().getExtras().get(EXTRA_DRINKNO); //Create a cursor and populate Views from the cursor's data. try { // Gain access to our app's database: SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase(); // Get the name, description, and image for drinkNo: Cursor cursor = db.query ("DRINK", new String[] {"NAME", "DESCRIPTION", "IMAGE_RESOURCE_ID", "FAVORITE"}, "_id = ?", new String[] {Integer.toString(drinkNo)}, null, null,null); // Move to the first record in the Cursor if (cursor.moveToFirst()) { // Get the drink details from the cursor String nameText = cursor.getString(0); String descriptionText = cursor.getString(1); int photoId = cursor.getInt(2); boolean isFavorite = (cursor.getInt(3) == 1); // Populate the drink name TextView name = (TextView)findViewById(R.id.name); name.setText(nameText); // Populate the drink description TextView description = (TextView)findViewById(R.id.description); description.setText(descriptionText); // Populate the drink image ImageView photo = (ImageView)findViewById(R.id.photo); photo.setImageResource(photoId); photo.setContentDescription(nameText); // Populate the favorite checkbox CheckBox favorite = (CheckBox)findViewById(R.id.favorite); favorite.setChecked(isFavorite); } // Close up shop. cursor.close(); db.close(); } catch (SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); toast.show(); } } // Update the DB's FAVORITES field for this drink. public void onFavoriteClicked(View view) { int drinkNo = (Integer)getIntent().getExtras().get("drinkNo"); new UpdateDrinkTask().execute(drinkNo); } // Inner class: a task to update the drink's DB record. private class UpdateDrinkTask extends AsyncTask { ContentValues drinkValues; @Override protected void onPreExecute() { CheckBox favorite = (CheckBox)findViewById(R.id.favorite); drinkValues = new ContentValues(); drinkValues.put("FAVORITE", favorite.isChecked()); Toast.makeText(DrinkActivity.this, "Backgrounding DB operation...", Toast.LENGTH_SHORT).show(); } @Override protected Boolean doInBackground(Integer... drinks) { int drinkNo = drinks[0]; SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(DrinkActivity.this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase(); db.update("DRINK", drinkValues, "_id = ?", new String[]{Integer.toString(drinkNo)}); db.close(); return true; } catch(SQLiteException e) { return false; } } @Override protected void onPostExecute(Boolean success) { if (!success) { Toast.makeText(DrinkActivity.this, "Database unavailable", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(DrinkActivity.this, "DB update complete", Toast.LENGTH_SHORT).show(); } } } } ===== Debug with the system log ===== This isn't covered until Chapter 13 in the book, but it's worth using here. In this final version of DrinkActivity, I use the [[android_learning:using_the_system_log|system log]] to output debug information to the console instead of using Toasts for debug messages. package com.hfad.starbuzz; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.os.AsyncTask; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; public class DrinkActivity extends AppCompatActivity { public static final String EXTRA_DRINKNO = "drinkNo"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink); int drinkNo = (Integer)getIntent().getExtras().get(EXTRA_DRINKNO); //Create a cursor and populate Views from the cursor's data. try { // Gain access to our app's database: SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase(); // Get the name, description, and image for drinkNo: Cursor cursor = db.query ("DRINK", new String[] {"NAME", "DESCRIPTION", "IMAGE_RESOURCE_ID", "FAVORITE"}, "_id = ?", new String[] {Integer.toString(drinkNo)}, null, null,null); // Move to the first record in the Cursor if (cursor.moveToFirst()) { // Get the drink details from the cursor String nameText = cursor.getString(0); String descriptionText = cursor.getString(1); int photoId = cursor.getInt(2); boolean isFavorite = (cursor.getInt(3) == 1); // Populate the drink name TextView name = (TextView)findViewById(R.id.name); name.setText(nameText); // Populate the drink description TextView description = (TextView)findViewById(R.id.description); description.setText(descriptionText); // Populate the drink image ImageView photo = (ImageView)findViewById(R.id.photo); photo.setImageResource(photoId); photo.setContentDescription(nameText); // Populate the favorite checkbox CheckBox favorite = (CheckBox)findViewById(R.id.favorite); favorite.setChecked(isFavorite); } // Close up shop. cursor.close(); db.close(); } catch (SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); toast.show(); } } // Update the DB's FAVORITES field for this drink. public void onFavoriteClicked(View view) { int drinkNo = (Integer)getIntent().getExtras().get("drinkNo"); new UpdateDrinkTask().execute(drinkNo); } // Inner class: a task to update the drink's DB record. private class UpdateDrinkTask extends AsyncTask { ContentValues drinkValues; @Override protected void onPreExecute() { CheckBox favorite = (CheckBox)findViewById(R.id.favorite); drinkValues = new ContentValues(); drinkValues.put("FAVORITE", favorite.isChecked()); Log.d("DB", "Backgrounding DB operation..."); } @Override protected Boolean doInBackground(Integer... drinks) { int drinkNo = drinks[0]; SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(DrinkActivity.this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase(); db.update("DRINK", drinkValues, "_id = ?", new String[]{Integer.toString(drinkNo)}); db.close(); return true; } catch(SQLiteException e) { return false; } } @Override protected void onPostExecute(Boolean success) { if (!success) { Toast.makeText(DrinkActivity.this, "Database unavailable", Toast.LENGTH_SHORT).show(); } else { Log.d("DB", "DB update complete"); } } } }