Listing 13.9 Goodbye Random Crimes

I’ve surpassed the AApt2 trouble and was able to execute the application and navigate to add a new crime. But as I added the code from 13.8 and 13.9 I’ve run into a run-time error.

I believe it could be my code but as far as I’ve seen from the solution mines matches. The program starts but when I click the “+” sign it crashes.

My Log:
I/OpenGLRenderer: Initialized EGL, version 1.4
D/OpenGLRenderer: Enabling debug mode 0
W/art: Before Android 4.1, method int android.support.v7.widget.ListViewCompat.lookForSelectablePosition(int, boolean) would have incorrectly overridden the package-private method in android.widget.ListView
I/Timeline: Timeline: Activity_idle id: android.os.BinderProxy@6461412 time:458765353
I/Timeline: Timeline: Activity_launch_request id:com.bignerdranch.android.criminalintent time:458770701
I/ViewRootImpl: CPU Rendering VSync enable = true
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.bignerdranch.android.criminalintent, PID: 20447
java.lang.NullPointerException: Attempt to invoke virtual method ‘boolean java.lang.Boolean.booleanValue()’ on a null object reference
at com.bignerdranch.android.criminalintent.CrimeFragment.onCreateView(CrimeFragment.java:139)
at android.support.v4.app.Fragment.performCreateView(Fragment.java:2354)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1419)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1740)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1809)
at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:799)
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2580)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2367)
at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2322)
at android.support.v4.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:2199)
at android.support.v4.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:651)
at android.support.v4.app.FragmentStatePagerAdapter.finishUpdate(FragmentStatePagerAdapter.java:167)
at android.support.v4.view.ViewPager.populate(ViewPager.java:1236)
at android.support.v4.view.ViewPager.populate(ViewPager.java:1084)
at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1614)
at android.view.View.measure(View.java:17550)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5579)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:436)
at android.support.v7.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:139)
at android.view.View.measure(View.java:17550)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5579)
at android.support.v7.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:400)
at android.view.View.measure(View.java:17550)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5579)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:436)
at android.view.View.measure(View.java:17550)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5579)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1436)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:722)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:613)
at android.view.View.measure(View.java:17550)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5579)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:436)
at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2635)
at android.view.View.measure(View.java:17550)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2034)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1192)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1398)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1080)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5940)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:773)
at android.view.Choreographer.doCallbacks(Choreographer.java:586)
at android.view.Choreographer.doFrame(Choreographer.java:556)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:759)
at android.os.Handler.handleCallback(Handler.java:743)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:5255)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:902)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:697)
I/Process: Sending signal. PID: 20447 SIG: 9
Disconnected from the target VM, address: ‘localhost:8601’, transport: ‘socket’

When I ran the debugger and stepped in to my code I was led here:

public Boolean isSolved()
{
return mSolved;
}

The debugger said the byte code and source code wasn’t a match for isSolved. The App will come on but when I click the new crime button it crashes.

When I ran the break point on the line in question:

androidError

CrimeLab class:

package com.bignerdranch.android.criminalintent;

import android.content.Context;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**

  • Created by JB on 12/15/2017.
    */

/*You are going to store the List of crimes in a singleton. A singleton is
a class that allows only one instance of itself to be created. A singleton
exists as long as the application stays in memory, so storing the
list in a singleton will keep the crime data available
throughout any lifecycle changes in your activities and fragments.

The CrimeLab singleton is not a solution for long-term storage of data,
but it does allow the app to have one owner of the crime data and
provides a way to easily pass that data between controller classes. */

public class CrimeLab
{
//8.1 Setting up the singleton
/*To create a singleton, you create
a class with a private constructor and a get() method. */

private static CrimeLab sCrimeLab;  //static variable
private List<Crime> mCrimes;

//get() for the singleton
public static CrimeLab get(Context context)
{
    if (sCrimeLab == null)
    {
        sCrimeLab = new CrimeLab(context);
    }
    return sCrimeLab;
}//end get () for singleton

/*In CrimeLab’s constructor, create an empty List of Crimes.
Also, add two methods:a getCrimes() method that returns the
List and a getCrime(UUID) that returns the Crime with the given ID. */

//private constructor for the singleton
private CrimeLab(Context context)
{
    mCrimes = new ArrayList<>();      //empty list of crimes

}//end private constructor for the singleton

//13.8 Adding a new crime
public void addCrime(Crime c)
{
    mCrimes.add(c);
}//end adding new crime

//getCrimes()
public List<Crime> getCrimes()
{
    return mCrimes;
}//end getCrimes()

//getCrime(UUID)
public Crime getCrime(UUID id)
{
    for (Crime crime : mCrimes)
    {
        if (crime.getId().equals(id))
        {
            return crime;
        }
    }
    return null;
}

}//end of crimeLab Class

CrimeListFragment Class:

package com.bignerdranch;

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
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.ImageView;
import android.widget.TextView;

import com.bignerdranch.android.criminalintent.Crime;
import com.bignerdranch.android.criminalintent.CrimeLab;
import com.bignerdranch.android.criminalintent.CrimePagerActivity;
import com.bignerdranch.android.criminalintent.R;

import java.util.List;

/**

  • Created by JB on 12/15/2017.
    */

public class CrimeListFragment extends Fragment
{
private RecyclerView mCrimeRecyclerView;
private CrimeAdapter mAdapter;

//13.7 Receiving menu callbacks
@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()));

    updateUI();

    return view;
}//end onCreateView()

/*In CrimeListFragment, override onResume() and trigger a call
 to updateUI() to reload the list. Modify the updateUI() method
to call notifyDataSetChanged() if the CrimeAdapter is already set up.
----------------------------------------------------------------------
10.9 Reloading the list in onResume()*/
@Override
public void onResume()
{
    super.onResume();
    updateUI();
}//end onResume()

//13.6 Inflating a menu source
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
    super.onCreateOptionsMenu(menu, inflater);
    inflater.inflate(R.menu.fragment_crime_list, menu);
}//end menu inflater

//13.10 Responding to menu selection
@Override
public boolean onOptionsItemSelected(MenuItem item)
{    switch (item.getItemId())
    {
        case R.id.new_crime:
            Crime crime = new Crime();
            CrimeLab.get(getActivity()).addCrime(crime);
            Intent intent = CrimePagerActivity
                    .newIntent(getActivity(), crime.getId());
            startActivity(intent);
            return true;
        default:
            return super.onOptionsItemSelected(item);

    }//end switch

}//end onOptionsItemSelected

//8.20 Setting an Adapter
private void updateUI()
{
    CrimeLab crimeLab = CrimeLab.get(getActivity());
    List<Crime> crimes = crimeLab.getCrimes();
    //10.9 Reloading the list
    if(mAdapter == null)
    {
        mAdapter = new CrimeAdapter(crimes);
        mCrimeRecyclerView.setAdapter(mAdapter);
    }//end if
    else
    {
        mAdapter.notifyDataSetChanged();
    }


}//end updateUI()

/*The next job is to define the ViewHolder that will inflate
 and own your layout. Define it as an inner class in CrimeListFragment. */

//the ViewHolder
private class CrimeHolder extends RecyclerView.ViewHolder implements
        View.OnClickListener
{
    private TextView mTitleTextView;
    private TextView mDateTextView;
    private Crime mCrime;
    private ImageView mSolvedImageView;

    public CrimeHolder(LayoutInflater inflater, ViewGroup parent)
    {
        super(inflater.inflate(R.layout.list_item_crime, parent, false));

        //8.24 Detecting presses in CrimeHolder
        itemView.setOnClickListener(this);

        //8.21 Pulling out view in the constructor
        mTitleTextView = (TextView) itemView.findViewById(R.id.crime_title);
        mDateTextView = (TextView) itemView.findViewById(R.id.crime_date);

        //9.3 Updating handcuff visibility to Crimes solved.
        mSolvedImageView = (ImageView) itemView.findViewById(R.id.crime_solved);
    }//end CrimeHolder constructor

    //8.22 Writing a bind(Crime) method
    public void bind(Crime crime)
    {
        mCrime = crime;
        mTitleTextView.setText(mCrime.getTitle());
        mDateTextView.setText(mCrime.getDate().toString());

        //9.3 Updating handcuff visibility to Crimes solved.
        mSolvedImageView.setVisibility(crime.isSolved() ? View.VISIBLE : View.GONE);
    }//end bind(Crime)

    //8.24 Detecting presses in CrimeHolder
    @Override
    public void onClick(View view)
    {
        //10.1 Removed toast now Starting CrimeActivity
        //10.3 Stashing and passing a crime
        //modify CrimeHolder.onClick(View) to start a CrimePagerActivity.
        Intent intent = CrimePagerActivity.newIntent(getActivity(), mCrime.getId());
        startActivity(intent);
    }//end onClick()

}//end CrimeHolder class/ the ViewHolder

//the adapter
private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder>
{
    private List<Crime> mCrimes;

    public CrimeAdapter(List<Crime> crimes)
    {
        mCrimes = crimes;
    }//end CrimeAdapter()


    @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)
    {
        //8.23 calling the bind(Crime)method
        Crime crime = mCrimes.get(position);
        holder.bind(crime);
    }

    @Override
    public int getItemCount()
    {
        return mCrimes.size();
    }
}//end CrimeAdapter class/ the adapter

}//end CrimeListFragment Class

Crime Class:

package com.bignerdranch.android.criminalintent;

import java.util.Date;
import java.util.UUID;

/**

  • Created by JB on 12/15/2017.
    */

public class Crime
{
/In Crime.java, add fields to
represent the crime’s ID, title, date, and
status and a constructor that initializes the ID and date fields
-----------------------------------------------------------------
7.3 Adding to Crime Class
/
private UUID mId; //ID
private String mTitle; //Title
private Date mDate; //Date
private Boolean mSolved; //status

// a constructor that initializes the ID and date fields
public Crime()
{
    mId = UUID.randomUUID();
    mDate = new Date();
}//end constructor

/*Next, you want to generate a getter for the read-only mId and
both a getter and setter for mTitle, mDate, and mSolved. */
//getter for mId
public UUID getId()
{
    return mId;
}

//getter and setters
public String getTitle()
{
    return mTitle;
}

public void setTitle(String title)
{
    mTitle = title;
}

public Date getDate()
{
    return mDate;
}

public void setDate(Date date)
{
    mDate = date;
}

public Boolean isSolved()
{
    return mSolved;
}

public void setSolved(Boolean solved) {
    mSolved = solved;
}

}//end Crime Class

I don’t understand how this line shows on my CrimeListFragment class but not in the solutions:

import com.bignerdranch.android.criminalintent.R;

I’ve researched the error why source code and byte code do not match and what to do and come up with nothing. I’ve been stalled at the recyclerview compatibility issue and the AAPT2 issue and found the solution through research but not this time. If anyone have any insight I would greatly appreciate it. I’ve started criminal intent over twice already and I won’t be starting over a third time, I’ll just invest my time programming something else.

Hey, I suggest you, Can you change the value (mSolved) Boolean to boolean in the Crime class?
like that

private boolean mSolved; //status

public boolean isSolved()
{
    return mSolved;
}

public void setSolved(boolean solved) {
    mSolved = solved;
}

For the More Curious: link

1 Like

I will certainly try and will let u know how it goes. Thank you and thanks again.

I just checked mines out. Currently mines is set up just like your example in Crime.java.

Is this issue solved?

Please provide the CrimeFragment.java and check whether mCrime was null on line 139 when the method isSolved() was called.

I haven’t solved it yet. I will post crimeFragment.java Thanks.

crimeFragment.java:

package com.bignerdranch.android.criminalintent;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;

import java.util.Date;
import java.util.UUID;

/**

  • Created by JB on 12/15/2017.
    */

//12.3 Working with alertDialog
/*In CrimeFragment, add a constant for the DatePickerFragment’s tag. Then,
in onCreateView(…), remove the code that disables the date button and set a View.
OnClickListener that shows a DatePickerFragment when the date button is pressed. */
public class CrimeFragment extends Fragment
{
//10.6 Writing a newInstance(UUID)method
private static final String ARG_CRIME_ID = “crime-id”;

//12.3 Showing your DialogFragment
//Constant for DatePickerFragment's tag
private static final String DIALOG_DATE = "DialogDate";

//create a constant for the request code and then make
// CrimeFragment the target fragment of the DatePickerFragment instance.
//12.8 Setting target fragment
//request code
private static final int REQUEST_DATE = 0;

/*(1st)In CrimeFragment.java, add a member variable for the Crime
instance and an implementation of Fragment.onCreate(Bundle).
-------------------------------------------------------------
7.16 Overriding the onCreate(Bundle)method*/
private Crime mCrime;                //member variable for crime
private EditText mTitleField;        //variable for EditText
private Button mDateButton;          //button
private CheckBox mSolvedCheckBox;     //var for checkBox

//10.6 Writing a newInstance(UUID)method
public static CrimeFragment newInstance(UUID crimeId)
{
    Bundle args = new Bundle();
    args.putSerializable(ARG_CRIME_ID, crimeId);
    CrimeFragment fragment = new CrimeFragment();
    fragment.setArguments(args);
    return fragment;
}//End newInstance(UUID)method


//an implementation of Fragment.onCreate(Bundle)
@Override
public void onCreate(@Nullable Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    //Retrieving the extra and fetching the crime
    //CrimeFragment fetches a Crime, its view can display that Crime’s data.
    UUID crimeId = (UUID)getArguments().getSerializable(ARG_CRIME_ID);
    mCrime = CrimeLab.get(getActivity()).getCrime(crimeId);

}//end of onCreate(Bundle)

/*(2nd)In CrimeFragment.java, add an implementation
of onCreateView(…) that inflates fragment_crime.xml*/

/*(3rd) You are now going to hook up the EditText, Checkbox, and Button
in your fragment. The onCreateView(…) method is the place to wire up these widgets. */

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
{
    View v = inflater.inflate(R.layout.fragment_crime,container, false);

    //(4th) Get a reference and listener for EditText
    mTitleField = (EditText) v.findViewById(R.id.crime_title);
    mTitleField.setText(mCrime.getTitle());       //10.5 Updating view objects
    mTitleField.addTextChangedListener(new TextWatcher()
    {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after)
        {
            //intentionally blank
        }// end of beforeTextChanged

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count)
        {
            mCrime.setTitle(s.toString());
        }//end of onTextChanged

        @Override
        public void afterTextChanged(Editable s)
        {
            //this one too
        }//end of afterTextChanged
    });//end of listener for EditText

    //7.13 Setting Button text
    mDateButton = (Button) v.findViewById(R.id.crime_date);
    updateDate();

    /*remove the code that disables the date button and set a View.
    OnClickListener that shows a DatePickerFragment when the date button is pressed.*/
    //mDateButton.setEnabled(false);     //disables the button so it wont respond to the user
    mDateButton.setOnClickListener(new View.OnClickListener()
    {
        @Override
        public void onClick(View v)
        {
            FragmentManager manager = getFragmentManager();
            /*In CrimeFragment, remove the call to the DatePickerFragment constructor and
            replace it with a call to DatePickerFragment.newInstance*/
            //old 1-DatePickerFragment dialog = new DatePickerFragment();
            DatePickerFragment dialog = DatePickerFragment.newInstance(mCrime.getDate());
            //make CrimeFragment the target fragment of the DatePickerFragment instance.
            dialog.setTargetFragment(CrimeFragment.this, REQUEST_DATE);
            dialog.show(manager, DIALOG_DATE);
        }
    });

    //7.14 Listening for checkBox changes
    mSolvedCheckBox = (CheckBox)v.findViewById(R.id.crime_solved);
    mSolvedCheckBox.setChecked(mCrime.isSolved());
    mSolvedCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
    {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
        {
            mCrime.setSolved(isChecked);
        }//end onCheckedChanged
    });//end of listener for CheckBox
    return v;
}//end of onCreateView()

//override onActivityResult(…) to retrieve the extra,
// set the date on the Crime, and refresh the text of the date button.
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
    if (resultCode != Activity.RESULT_OK)
    {
        return;
    } //end if
    if (requestCode == REQUEST_DATE)
    {
        Date date = (Date) data.getSerializableExtra(DatePickerFragment.EXTRA_DATE);
        mCrime.setDate(date);
        updateDate();
    } //end if
}//end onActivityResult

//The code that sets the button’s text is identical to code you
// call in onCreateView(…). To avoid setting the text in two places,
// encapsulate this code in a private updateDate()
// method and then call it in onCreateView(…) and onActivityResult(…).
//12.12 Highlighting date button update
private void updateDate()
{
    mDateButton.setText(mCrime.getDate().toString());
}//end updateDate()

}//end of fragment

Please add a debug point at this line in CrimeFragment.java to check whether mCrime is assigned successfully.

mCrime = CrimeLab.get(getActivity()).getCrime(crimeId);

Okay, I’m checking now.

Yes you were right. It’s null.

In CrimeListFragment.java, add one line as follows. Not sure whether it will solve the issue.

    private void updateUI() {
        CrimeLab crimeLab = CrimeLab.getInstance(getActivity());
        List<Crime> crimes = crimeLab.getCrimes();

        if (mCrimeAdapter == null) {
            mCrimeAdapter = new CrimeAdapter(crimes);
            mCrimeRecyclerView.setAdapter(mCrimeAdapter);
        } else {
            mCrimeAdapter.setCrimes(crimes);     // add this line
            mCrimeAdapter.notifyDataSetChanged();
        }
    }

Thank you. I will try now.