这节课是 Android 开发(入门)课程 的第四部分《数据与数据库》的最后一节课,导师依然是 Jessica Lin 和 Katherine Kuan。这节课主要内容有两个部分:
- 利用上节课完成的 Content Provider 实现 CursorLoader,以在后台线程加载数据,使 UI 中的数据列表保持活跃状态。
- 对 Pets App 进行逻辑完善和细节优化。
关键词:CursorAdapter、CursorLoader、Notification URI、UP & BACK Button、AlertDialog、invalidateOptionsMenu & onPrepareOptionsMenu
CursorAdapter
在 Pets App 中,CatalogActivity 显示的列表使用 ListView 实现,自然需要应用适配器模式。之前在《课程 2: 数据,列表,循环和自定义类》中提到,适配器模式能够提供视图回收的好处,这是 Android 的一个重要内存策略。同时,适配器也决定了列表子项的布局,由于这里的数据来源是 Cursor,所以需要使用 Android 提供的 CursorAdapter 作为 ListView 的适配器。
CursorAdapter 是一个抽象类,需要 override 的方法有两个:
View newView(Context context, Cursor cursor, ViewGroup parent)
void bindView(View view, Context context, Cursor cursor)
两个方法配合工作,实现 ListView 列表各个的子项显示:
- 首先通过
newView
创建新的子项视图,此时视图不包含数据。 - 随后通过
bindView
将数据填充到视图中,其中输入参数
(1)View view: 即newView
已创建的视图。
(2)Cursor cursor: 即要填充的 Cursor 数据,此时移动 Cursor 位置(行)的操作已自动完成。
每当显示新的 ListView 列表子项时,CursorAdapter 都要先通过 newView
创建新的子项视图,随后通过 bindView
将数据填充到视图中;而当视图回收时,CursorAdapter 就可以直接通过 bindView
将数据填充到回收的视图中,无需再通过 newView
创建新的子项视图,这也是 CursorAdapter 将 ListView 列表的子项显示分为 newView
和 bindView
两个方法实现的原因。
当然,这些过程都是自动完成的,开发者只需要 override 上述两个方法即可。例如在 Pets App 中,PetCursorAdapter 作为 CatalogActivity 的 ListView 的适配器,实现其列表显示。ListView 与 CursorAdapter 的更多应用信息可以参考这个 CodePath 教程。
In PetCursorAdapter.java
public class PetCursorAdapter extends CursorAdapter {
public PetCursorAdapter(Context context, Cursor c) {
super(context, c, 0 /* flags */);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView nameTextView = (TextView) view.findViewById(R.id.name);
TextView summaryTextView = (TextView) view.findViewById(R.id.summary);
int nameColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_NAME);
int breedColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_BREED);
String petName = cursor.getString(nameColumnIndex);
String petBreed = cursor.getString(breedColumnIndex);
if (TextUtils.isEmpty(petBreed)) {
petBreed = context.getString(R.string.unknown_breed);
}
nameTextView.setText(petName);
summaryTextView.setText(petBreed);
}
}
- 在 PetCursorAdapter 构造函数内调用超级类的构造函数进行初始化,以继承 CursorAdapter 的特性。
- Override
newView
方法直接返回根据 list_item 布局构造的 View 对象。 - Override
bindView
方法将传入的 Cursor 数据填充至传入的 View 中,具体的做法是:
(1)通过view.findViewById
方法找到需要填充数据的视图,这里是两个 TextView。
(2)通过cursor.getColumnIndex
方法并根据 Contract 中定义的数据库列名找到所需的 Cursor 键名,随后通过cursor.getString
方法获取对应键的值,这里是字符串。
(3)通过setText
方法将数据填充进视图中。 - 利用
TextUtils.isEmpty
方法可以判断字符串是否为空。
Note:
与 ArrayAdapter 通过 getView
方法来实现 ListView 列表的子项显示不同,CursorAdapter 需要使用 newView
和 bindView
两个方法。不过事实上,CursorAdapter 中也存在 getView
方法,从 源码 可以看出,getView
会根据当前情况下是否存在可回收的视图,来决定是否调用 newView
创建新的子项视图。
因此,CursorAdapter 在实现 ListView 列表的子项显示时,与 ArrayAdapter 一样调用 getView
方法,而 newView
和 bindView
两个方法更像是辅助方法的概念;只不过对于开发者而言,只需要 override newView
和 bindView
两个方法,无需关心其中的逻辑。
CursorLoader
设置好显示 Cursor 数据的列表后,接下来将利用上节课完成的 Content Provider 实现 CursorLoader,以在后台线程加载数据,使 UI 中的数据列表保持活跃状态,包括添加或删除数据时列表自动增加或减少一行数据。
CursorLoader 是 AsyncTaskLoader 的子类,它会通过 URI 查询 ContentResolver (不是 Content Provider)以获取 Cursor 对象。与《课程 3: 线程与并行》中提到的概念一样,CursorLoader 作为一种 Loader,它也会在后台线程中进行耗时较长的数据库查询任务,不会阻塞 UI 线程而导致 ANR;同时,CursorLoader 能够在数据变化时,使用相同的 URI 重新查询数据,这保证了 UI 中的数据列表始终处于最新状态。
因此,在 Pets App 中引入 CursorLoader 在后台线程加载数据,步骤与 AsyncTaskLoader 的类似:
一、引入 CursorLoader
In CatalogActivity.java
public class CatalogActivity extends AppCompatActivity
implements LoaderManager.LoaderCallbacks {
private static final int PET_LOADER = 0;
PetCursorAdapter mCursorAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_catalog);
...
ListView petListView = (ListView) findViewById(R.id.list);
mCursorAdapter = new PetCursorAdapter(this, null);
petListView.setAdapter(mCursorAdapter);
getLoaderManager().initLoader(PET_LOADER, null, this);
}
...
}
- 一个 Activity 或 Fragment 内只有一个 LoaderManager,它可以管理多个 Loader;而不同 Loader 之间的唯一标识是 ID,它可以是任意数字。因此在 CatalogActivity 中定义一个全局常量作为 CursorLoader 的 ID,并传入
initLoader
方法。 -
initLoader
方法的第三个输入参数是 Loader 的回调对象,设置为this
表示回调对象即 Activity 本身,回调函数放在 Activity 内,在 Activity 类名后面添加 implements 参数。 - 在这里,确保 CursorAdapter 定义为全局变量,并在
onCreate
方法中新建一个对象,暂时将输入参数 Cursor 设为 null,并设置为 ListView 的适配器。
二、实现 LoaderManager.LoaderCallbacks 的三个回调函数
In CatalogActivity.java
@Override
public Loader onCreateLoader(int i, Bundle bundle) {
String[] projection = {
PetEntry._ID,
PetEntry.COLUMN_PET_NAME,
PetEntry.COLUMN_PET_BREED };
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) {
mCursorAdapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader loader) {
mCursorAdapter.swapCursor(null);
}
- Activity 或 Fragment 内的 LoaderManager 会自动管理 Loader 对象,所以开发者几乎不需要直接操作 Loader,往往是通过回调函数来处理数据加载的事件。
- 在
onCreateLoader
方法中,创建并返回一个新的 CursorLoader 对象。在此之后,CursorLoader 就会在后台线程开始查询数据,在查询结束后调用onLoadFinished
方法,其输入参数包含获取的 Cursor 数据。 - 与在 Quake Report App 中使用 AsyncTaskLoader 的自定义类不同,CursorLoader 是一个具象类,所以它可以直接调用,关键输入参数为 Content URI 和想要读取的列数据(字符串数组,注意一定要有 "_id" 列)。
- 在
onLoadFinished
方法中,调用 CursorAdapter 的swapCursor
方法,将获取的 Cursor 数据传入 CursorAdapter 用于列表显示。在这里,开发者无需调用cursor.close()
来释放资源,因为 CursorLoader 会自动处理无用的旧数据。 - 在
onLoaderReset
方法中,同样调用 CursorAdapter 的swapCursor
方法,但传入 null,表示清除 CursorAdapter 的数据,使列表清空。这种情况会在当前 Loader 被销毁,或者最新的 Cursor 数据无效时发生,同时这也会防止内存泄漏。
尽管 CursorLoader 设置完成了,但目前还没有建立一个数据变化与数据更新之间的沟通机制。CursorLoader 需要仅在数据变化时重新获取数据,在类似设备屏幕旋转、重启应用等情况时数据保持不变,避免不必要的数据重新加载。
已知应用在 UI 端与数据库端之间的数据传递主要依靠 URI 和 CRUD 对应的方法,所以最好的做法是在 Content Provider 的四个 CRUD 方法中建立一个依靠 URI 传递数据的沟通机制。也就是说:
- 在 Content Provider 的
query
方法中设置一个 Notification URI,表示 CursorLoader 需要观察数据变化的内容。
(1)如果 Notification URI 为整个表格,那么表格中的任何数据变化都会触发 CursorLoader 重新加载数据。
(2)如果 Notification URI 为整个表格的其中一行,如第三行,那么 CursorLoader 仅在该行发生变化时起作用,如新增一个第七行时不会触发 CursorLoader 动作。 - 分别在 Content Provider 的
insert
、update
、delete
方法中通过 URI 告知 CursorLoader 数据发生了变化,使 CursorLoader 重新加载数据;
Query
因此,在 Pets App 中,首先在 Content Provider 的 query
方法内通过 setNotificationUri
方法设置 Notification URI,其中输入参数包含应用环境的 Content Resolver。
In PetProvider.java
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor cursor;
...
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
Insert
在 Pets App 中,由于 Content Provider 的 insert
方法调用了 insertPet
辅助方法,所以在该方法内调用 ContentResolver 的 notifyChange
方法告知 CursorLoader 重新加载数据。其中第一个参数为 URI,第二个参数为可选的 ContentObserver 参数,传入 null 表示默认 CursorAdapter 将作为收到通知的对象,自动触发 CursorLoader 重新加载数据的动作。
In PetProvider.java
private Uri insertPet(Uri uri, ContentValues values) {
...
getContext().getContentResolver().notifyChange(uri, null);
...
}
Update
类似地,对于 Content Provider 的 update
方法,在 updatePet
辅助方法内调用 ContentResolver 的 notifyChange
方法告知 CursorLoader 重新加载数据。不过在这里需要先检查是否真正发生了数据更新,若是才执行指令,避免不必要的重新加载。
In PetProvider.java
private int updatePet(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
...
int rowsUpdated = database.update(PetEntry.TABLE_NAME, values, selection, selectionArgs);
if (rowsUpdated != 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return rowsUpdated;
}
Delete
类似地,对于 Content Provider 的 delete
方法,在检查到真正发生了数据删除后,调用 ContentResolver 的 notifyChange
方法告知 CursorLoader 重新加载数据。
In PetProvider.java
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
...
if (rowsDeleted != 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return rowsDeleted;
}
至此,CursorLoader 可以通过 Content Provider 在后台线程加载数据,并在数据变化时保持 UI 中的数据列表始终处于最新状态。而事实上,CursorLoader 的应用非常广泛,属于 Android 中最常用的一种 Loader。例如在 Pets App 的 EditorActivity 中,利用 CursorLoader 在后台线程获取由 Intent 传递过来的 URI 指向的 Cursor 数据,并把它们设置到相应的视图中。
In CatalogActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_catalog);
...
petListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> adapterView, View view, int position, long id) {
Intent intent = new Intent(CatalogActivity.this, EditorActivity.class);
Uri currentPetUri = ContentUris.withAppendedId(PetEntry.CONTENT_URI, id);
intent.setData(currentPetUri);
startActivity(intent);
}
});
}
- 在 CatalogActivity 中,设置 ListView 的 OnItemClickListener 为 Intent 到 EditorActivity,并且通过
setData
方法带上被点击项目的 Content URI。 - 通过 ContentUris 的
withAppendedId
构造一个带被点击项目 ID 的 Content URI。
In EditorActivity.java
private static final int EXISTING_PET_LOADER = 0;
private Uri mCurrentPetUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_editor);
Intent intent = getIntent();
mCurrentPetUri = intent.getData();
if (mCurrentPetUri == null) {
setTitle(getString(R.string.editor_activity_title_new_pet));
} else {
setTitle(getString(R.string.editor_activity_title_edit_pet));
getLoaderManager().initLoader(EXISTING_PET_LOADER, null, this);
}
...
}
- 在 EditorActivity 中,通过
getIntent().getData()
获取由 Intent 传递过来的 URI 数据。
- 由于 EditorActivity 存在“新建”和“编辑”两种模式,而两者可以根据是否有 Intent 传递的 Data 区分,所以在这里通过 if/else 语句判断两种模式,进行代码分流。其中,通过
setTitle
方法分别设置两种模式下 Activity 应用栏的标题,此时可以删去 AndroidManifest 中 EditorActivity 的android:label
属性。 - 当 EditorActivity 处于“编辑”模式时,引入 CursorLoader,在后台线程获取由 Intent 传递过来的 URI 指向的 Cursor 数据,并把它们设置到相应的视图中。
In EditorActivity.java
public class EditorActivity extends AppCompatActivity implements
LoaderManager.LoaderCallbacks {
...
@Override
public Loader onCreateLoader(int i, Bundle bundle) {
String[] projection = {
PetEntry._ID,
PetEntry.COLUMN_PET_NAME,
PetEntry.COLUMN_PET_BREED,
PetEntry.COLUMN_PET_GENDER,
PetEntry.COLUMN_PET_WEIGHT };
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) {
if (cursor == null || cursor.getCount() < 1) {
return;
}
if (cursor.moveToFirst()) {
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);
String name = cursor.getString(nameColumnIndex);
String breed = cursor.getString(breedColumnIndex);
int gender = cursor.getInt(genderColumnIndex);
int weight = cursor.getInt(weightColumnIndex);
mNameEditText.setText(name);
mBreedEditText.setText(breed);
mWeightEditText.setText(Integer.toString(weight));
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) {
mNameEditText.setText("");
mBreedEditText.setText("");
mWeightEditText.setText("");
mGenderSpinner.setSelection(0);
}
}
- 与 CatalogActivity 中的 CursorLoader 类似,在
onCreateLoader
方法中创建并返回一个新的 CursorLoader 对象,其中 URI 输入参数就是由 Intent 传递过来的数据,在这里设置成了全局变量 mCurrentPetUri。在此之后,CursorLoader 就会在后台线程开始查询 URI 指定的 Cursor 数据,在查询结束后调用onLoadFinished
方法,其输入参数包含获取的数据。 - 在
onLoadFinished
中将传入的 Cursor 数据设置到相应的视图中,具体的做法与 CursorAdapter 的bindView
方法类似。值得注意的有三点:
(1)首先判断传入的 Cursor 数据是否为空,若是则提前返回,不执行任何其它操作。
(2)由于正常情况下 Cursor 仅有一行数据,因此通过if (cursor.moveToFirst()
判断语句确保仅在 Cursor 指向首行时执行任何其它操作。
(3)通过setSelection
设置 Spinner 默认选中的项目。 - 当 CursorLoader 被销毁时,在
onLoaderReset
中将相应的视图设为空或恢复为默认状态。
Override UP & BACK Button
在 Pets App 中,用户在 EditorActivity 点击向上 (UP) 或返回 (BACK) 按钮时,应用会直接退出 EditorActivity,而不会保存任何内容。因此,为了完善应用的业务逻辑,避免用户丢失工作,可以在这里弹出一个对话框,警告用户尚有未保存的更改。
一、监听是否进行了更改
In EditorActivity.java
private boolean mPetHasChanged = false;
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);
setContentView(R.layout.activity_editor);
...
mNameEditText.setOnTouchListener(mTouchListener);
mBreedEditText.setOnTouchListener(mTouchListener);
mWeightEditText.setOnTouchListener(mTouchListener);
mGenderSpinner.setOnTouchListener(mTouchListener);
}
- 定义一个全局变量 mPetHasChanged 作为编辑器是否被点击过的指示器,默认为 false,表示编辑器没有被点击;在 mTouchListener 监听器的
onTouch
方法中设为 true,表示有编辑器被点击。 - 在
onCreate
方法中将四个编辑器的 OnTouchListener 设为 mTouchListener 监听器,这体现了监听器模式的优势,即一个监听器可以应用到多个视图中。
二、创建警告对话框的辅助方法
In EditorActivity.java
private void showUnsavedChangesDialog(DialogInterface.OnClickListener discardButtonClickListener) {
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) {
if (dialog != null) {
dialog.dismiss();
}
}
});
AlertDialog alertDialog = builder.create();
alertDialog.show();
}
在这里用到了 AlertDialog,并把创建对话框的代码封装成一个方法,之前在《实战项目 9: 习惯记录应用》提到过;不同的是,这里要求传入一个 OnClickListener 作为 PositiveButton 的监听器,这种设计是因为向上 (UP) 与返回 (BACK) 按钮之间 PositiveButton 的操作不同。
三、Override BACK Button
In EditorActivity.java
@Override
public void onBackPressed() {
if (!mPetHasChanged) {
super.onBackPressed();
return;
}
DialogInterface.OnClickListener discardButtonClickListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
finish();
}
};
showUnsavedChangesDialog(discardButtonClickListener);
}
- 自定义返回 (BACK) 按钮的逻辑需要 override
onBackPressed
方法。 - 在
onBackPressed
方法内,首先判断全局变量 mPetHasChanged 是否为真。若为假,说明没有编辑器被点击过,所以调用其超级类,使返回 (BACK) 按钮的逻辑保持默认;并提前返回结束方法,不再执行任何其它操作。 - 如果有任一编辑器被点击,首先定义 AlertDialog 的 PositiveButton 的 OnClickListener,在这里是直接调用
finish()
方法关闭 Activity;然后将适配器对象传入上述创建对话框的辅助方法,以弹出对话框供用户选择。
四、Override UP Button
In EditorActivity.java
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
...
case android.R.id.home:
if (!mPetHasChanged) {
NavUtils.navigateUpFromSameTask(EditorActivity.this);
return true;
}
DialogInterface.OnClickListener discardButtonClickListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
NavUtils.navigateUpFromSameTask(EditorActivity.this);
}
};
showUnsavedChangesDialog(discardButtonClickListener);
return true;
}
return super.onOptionsItemSelected(item);
}
- 自定义向上 (UP) 按钮的逻辑需要在
onOptionsItemSelected
方法内添加一个android.R.id.home
case,注意不是android.R.id.back
或android.R.id.up
。 - 在
android.R.id.home
case 下,首先判断全局变量 mPetHasChanged 是否为真。若为假,说明没有编辑器被点击过,所以调用 NavUtils 的navigateUpFromSameTask
方法,关闭当前 Activity,将页面跳至其父级 Activity,即 CatalogActivity;并提前返回结束方法,不再执行任何其它操作。 - 如果有任一编辑器被点击,首先定义 AlertDialog 的 PositiveButton 的 OnClickListener,在这里是调用 NavUtils 的
navigateUpFromSameTask
方法,关闭当前 Activity,将页面跳至其父级 Activity,即 CatalogActivity;然后将适配器对象传入上述创建对话框的辅助方法,以弹出对话框供用户选择。
上述返回 (BACK) 和向上 (UP) 按钮的逻辑差别体现了两者导航模式的差别。正如《课程 5: Fragment》中提到的:
在导航的概念中,“向上”和“返回”按钮 (Up and Back buttons) 两者很容易混淆。
- “向上”按钮通常位于屏幕的左上角。它返回的是本应用内层级结构中上一层的页面,直到本应用的主页,所以“向上”按钮不会跳出本应用。例如在邮件应用内,点击邮件详情页左上角的“向上”按钮会返回到邮件列表页,如果邮件列表页是应用的主页,那么这里通常没有“向上”按钮。
- “返回”按钮显示在屏幕的底部,属于系统导航按钮 (Home、Menus、Back) 的其中一个,在一些 Android 设备上是实体按键。它返回的是按时间记录的上一个浏览页面,浏览页面不仅限于本应用,所以“返回”按钮有可能将用户导航到本应用外。例如当用户在观看视频时收到邮件提醒,如果用户点击提醒查看邮件详情,那么用户在邮件详情页点击“返回”按钮,就返回到先前的视频了,而不是返回到邮件列表页。
“返回”按钮还可用于关闭悬浮窗口,隐藏输入法,取消选中的高亮项目(如选中的文字以及弹出的“复制”操作栏)。
在运行时变更菜单选项
在 Pets App 中,EditorActivity 存在“新建”和“编辑”两种模式,其中“编辑”模式下有一个溢出菜单选项,用于删除当前数据,而“新建”模式应该隐藏这个选项。为了完善这一业务逻辑,应用需要在运行时变更菜单选项,这需要两个步骤。
首先,调用 invalidateOptionsMenu()
方法,告知 Android 当前菜单已发生变更,使应用执行 onPrepareOptionsMenu
方法,重新绘制菜单。关于 invalidateOptionsMenu()
方法作用的更多详解可以参考这个 stack overflow 帖子。
最后,override onPrepareOptionsMenu
方法,通过全局变量 mCurrentPetUri 判断 EditorActivity 当前处于“新建”状态时,就通过 menu.findItem
方法找到需要变更的选项,随后利用选项的 setVisible
方法设置其可见性。
In EditorActivity.java
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
if (mCurrentPetUri == null) {
MenuItem menuItem = menu.findItem(R.id.action_delete);
menuItem.setVisible(false);
}
return true;
}