Hi @cstewart,
I am getting the following fatal exception when I swipe right to delete a Crime listed in the CrimeListFragment:
09-15 17:31:35.801 4526-4526/com.bignerdranch.android.criminalintent E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.bignerdranch.android.criminalintent, PID: 4526
java.lang.NullPointerException: Attempt to invoke virtual method ‘java.lang.Class java.lang.Object.getClass()’ on a null object reference
at android.support.v4.app.BackStackRecord.doAddOp(BackStackRecord.java:380)
at android.support.v4.app.BackStackRecord.replace(BackStackRecord.java:430)
at android.support.v4.app.BackStackRecord.replace(BackStackRecord.java:421)
at com.bignerdranch.android.criminalintent.CrimeListActivity.onCrimeDeleted(CrimeListActivity.java:39)
at com.bignerdranch.android.criminalintent.CrimeListFragment$1.onSwiped(CrimeListFragment.java:82)
at android.support.v7.widget.helper.ItemTouchHelper$4.run(ItemTouchHelper.java:686)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
Here is the code for the following:
CrimeListActivity.java
package com.bignerdranch.android.criminalintent;
import android.content.Intent;
import android.support.v4.app.Fragment;
public class CrimeListActivity extends SingleFragmentActivity
implements CrimeListFragment.Callbacks, CrimeFragment.Callbacks {
@Override
protected Fragment createFragment() {
return new CrimeListFragment();
}
@Override
protected int getLayoutResId() {
return R.layout.activity_masterdetail;
}
@Override
public void onCrimeSelected(Crime crime) {
if (findViewById(R.id.detail_fragment_container) == null) { // phone
Intent intent = CrimePagerActivity.newIntent(this, crime.getID());
startActivity(intent);
} else { // tablet
Fragment newDetail = CrimeFragment.newInstace(crime.getID());
getSupportFragmentManager().beginTransaction()
.replace(R.id.detail_fragment_container, newDetail)
.commit();
}
}
@Override
public void onCrimeDeleted(Crime crime) {
if (findViewById(R.id.detail_fragment_container) == null) { // phone
} else { // tablet
getSupportFragmentManager().beginTransaction()
.replace(R.id.detail_fragment_container, null)
.commit();
}
}
@Override
public void onCrimeUpdated(Crime crime) {
CrimeListFragment listFragment = (CrimeListFragment)
getSupportFragmentManager()
.findFragmentById(R.id.fragment_container);
listFragment.updateUI();
}
}
CrimeListFragment.java
package com.bignerdranch.android.criminalintent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.List;
public class CrimeListFragment extends Fragment {
private int clickedCrimePosition;
private static final String CLICKED_CRIME_POSITION_ID = "clicked_crime_position_id";
private static final String SAVED_SUBTITLE_VISIBLE = "subtitle";
private static final int REQUEST_CRIME = 1;
private RecyclerView mCrimeRecyclerView;
private CrimeAdapter mAdapter;
private boolean mSubtitleVisible;
private LinearLayout mEmptyCrimeListLinearLayout;
private Button mEmptyCrimeListAddButton;
private Callbacks mCallbacks;
/**
* Required interface for hosting activities
*/
public interface Callbacks {
void onCrimeSelected(Crime crime);
void onCrimeDeleted(Crime crime);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mCallbacks = (Callbacks) context;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_crime_list, container, false);
mCrimeRecyclerView = (RecyclerView) view.findViewById(R.id.crime_recycler_view);
mCrimeRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
//Set up ItemTouchHelper for Swipe Right to delete
ItemTouchHelper mIth = new ItemTouchHelper(
new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
public boolean onMove(RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
return false;
}
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
final int pos = viewHolder.getAdapterPosition();
Crime crime = mAdapter.getCrimes().get(pos);
if (direction==ItemTouchHelper.RIGHT) {
mCallbacks.onCrimeDeleted(crime);
}
}
});
mIth.attachToRecyclerView(mCrimeRecyclerView); // Attach ItemTouchHelper to RecyclerView
mEmptyCrimeListLinearLayout = (LinearLayout) view.findViewById(R.id.empty_crime_list_layout);
mEmptyCrimeListAddButton = (Button) view.findViewById(R.id.add_crime_button);
if (savedInstanceState != null) {
clickedCrimePosition = savedInstanceState.getInt(CLICKED_CRIME_POSITION_ID);
mSubtitleVisible = savedInstanceState.getBoolean(SAVED_SUBTITLE_VISIBLE);
}
updateUI();
return view;
}
@Override
public void onResume() {
super.onResume();
updateUI();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_crime_list, menu);
MenuItem subtitleItem = menu.findItem(R.id.show_subtitle);
if (mSubtitleVisible) {
subtitleItem.setTitle(R.string.hide_subtitle);
} else {
subtitleItem.setTitle(R.string.show_subtitle);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.new_crime:
addCrime();
return true;
case R.id.show_subtitle:
mSubtitleVisible = !mSubtitleVisible;
getActivity().invalidateOptionsMenu();
updateSubtitle();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void addCrime() {
Crime crime = new Crime();
CrimeLab.get(getActivity()).addCrime(crime);
updateUI();
mCallbacks.onCrimeSelected(crime);
}
private void updateSubtitle() {
CrimeLab crimeLab = CrimeLab.get(getActivity());
int crimeCount = crimeLab.getCrimes().size();
String subtitle = getResources()
.getQuantityString(R.plurals.subtitle_plural, crimeCount, crimeCount);
if (!mSubtitleVisible) {
subtitle = null;
}
AppCompatActivity activity = (AppCompatActivity) getActivity();
activity.getSupportActionBar().setSubtitle(subtitle);
}
public void updateUI() {
CrimeLab crimeLab = CrimeLab.get(getActivity());
List<Crime> crimes = crimeLab.getCrimes();
if (mAdapter == null) {
mAdapter = new CrimeAdapter(crimes);
mCrimeRecyclerView.setAdapter(mAdapter);
} else {
mAdapter.setCrimes(crimes);
mAdapter.notifyDataSetChanged();
}
if (crimes.size() > 0) {
mEmptyCrimeListLinearLayout.setVisibility(View.GONE);
mCrimeRecyclerView.setVisibility(View.VISIBLE);
} else {
mEmptyCrimeListLinearLayout.setVisibility(View.VISIBLE);
mCrimeRecyclerView.setVisibility(View.GONE);
mEmptyCrimeListAddButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addCrime();
}
});
}
updateSubtitle();
}
private class CrimeHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView mTitleTextView;
private TextView mDateTextView;
private ImageView mSolvedImageView;
private Crime mCrime;
public CrimeHolder(LayoutInflater inflater, ViewGroup parent) {
super(inflater.inflate(R.layout.list_item_crime, parent, false));
itemView.setOnClickListener(this);
mTitleTextView = (TextView) itemView.findViewById(R.id.crime_title);
mDateTextView = (TextView) itemView.findViewById(R.id.crime_date);
mSolvedImageView = (ImageView) itemView.findViewById(R.id.crime_solved);
}
public void bind(Crime crime) {
mCrime = crime;
mTitleTextView.setText(mCrime.getTitle());
DateFormat dateFormat = new SimpleDateFormat(CrimeFragment.DATE_FORMAT);
DateFormat timeFormat = new SimpleDateFormat(CrimeFragment.TIME_FORMAT);
mDateTextView.setText(dateFormat.format(mCrime.getDate()) + " " + timeFormat.format(mCrime.getTime()));
mSolvedImageView.setVisibility(crime.isSolved() ? View.VISIBLE : View.GONE);
}
@Override
public void onClick(View v) {
clickedCrimePosition = getAdapterPosition();
mCallbacks.onCrimeSelected(mCrime);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CRIME) {
// Handle result
}
}
private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder> {
private List<Crime> mCrimes;
public CrimeAdapter(List<Crime> crimes) {
mCrimes = crimes;
}
@Override
public CrimeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
return new CrimeHolder(layoutInflater, parent);
}
@Override
public void onBindViewHolder(CrimeHolder holder, int position) {
Crime crime = mCrimes.get(position);
holder.bind(crime);
}
@Override
public int getItemCount() {
return mCrimes.size();
}
public void setCrimes(List<Crime> crimes) {
mCrimes = crimes;
}
public List<Crime> getCrimes() {
return mCrimes;
}
}
@Override
public void onSaveInstanceState(Bundle onSavedInstanceState) {
super.onSaveInstanceState(onSavedInstanceState);
onSavedInstanceState.putSerializable(CLICKED_CRIME_POSITION_ID, clickedCrimePosition);
onSavedInstanceState.putBoolean(SAVED_SUBTITLE_VISIBLE, mSubtitleVisible);
}
@Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
}