1

I'm building my first Android app (basic calculator) which consists of one Activity and a single fragment. The layout of the app (display and buttons) are defined in the fragment xml fragment_main.xml and should be inflated on startup. However, the app immediately crashed with a NPE. I'm confused as to whether I should set the click listeners for the calculator buttons in the Activity's onCreate() method, or in the fragment class's onCreateView() method. Code and stack trace below.

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.github.idclark.calculator/com.github.idclark.calculator.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
 Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.setOnClickListener(android.view.View$OnClickListener)' on a null object reference

public class MainActivity extends ActionBarActivity implements OnClickListener {

    private TextView mCalculatorDisplay;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, new CalculatorFragment())
                    .commit();
        }

        mCalculatorDisplay = (TextView) findViewById(R.id.textView);

        findViewById(R.id.AC).setOnClickListener(this);
        findViewById(R.id.plusminus).setOnClickListener(this);
        findViewById(R.id.percent).setOnClickListener(this);
        findViewById(R.id.nine).setOnClickListener(this);
        findViewById(R.id.eight).setOnClickListener(this);
        findViewById(R.id.seven).setOnClickListener(this);
        findViewById(R.id.six).setOnClickListener(this);
        findViewById(R.id.five).setOnClickListener(this);
        findViewById(R.id.four).setOnClickListener(this);
        findViewById(R.id.three).setOnClickListener(this);
        findViewById(R.id.two).setOnClickListener(this);
        findViewById(R.id.one).setOnClickListener(this);
        findViewById(R.id.zero).setOnClickListener(this);
        findViewById(R.id.div).setOnClickListener(this);
        findViewById(R.id.mult).setOnClickListener(this);
        findViewById(R.id.plus).setOnClickListener(this);
        findViewById(R.id.minus).setOnClickListener(this);
        findViewById(R.id.dec).setOnClickListener(this);
        findViewById(R.id.equal).setOnClickListener(this);
    }

@Override
    public void onClick(View v) {

        String buttonPressed = ((Button )v).getText().toString();
        mCalculatorDisplay.setText(buttonPressed);
    }

    /**
     * A placeholder fragment containing a simple view.
     */
    public static class CalculatorFragment extends Fragment {

        public CalculatorFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);
            return rootView;
        }
    }
}

Activity_Main

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container"
    android:layout_width="match_parent" android:layout_height="match_parent"
    tools:context=".MainActivity" tools:ignore="MergeRootFrame" />

Fragment_Main

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity$PlaceholderFragment"
    android:orientation="vertical">


<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceLarge"
    android:text="0"
    android:id="@+id/textView"
    android:layout_alignEnd="@+id/textView"
    android:textSize="65dp" />

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/AC"
        android:id="@+id/AC"
        android:layout_weight="0.25" />
    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/plusminus"
        android:id="@+id/plusminus"
        android:layout_weight="0.25" />
    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/percent"
        android:id="@+id/percent"
        android:layout_weight="0.25" />
    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/div"
        android:id="@+id/div"
        android:layout_weight="0.25" />

</LinearLayout>

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/nine"
        android:id="@+id/nine"
        android:layout_weight="0.25"/>
    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/eight"
        android:id="@+id/eight"
        android:layout_weight="0.25"/>
    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/seven"
        android:id="@+id/seven"
        android:layout_weight="0.25"/>
    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:id="@+id/mult"
        android:text="@string/mult"
        android:layout_weight="0.25"/>

</LinearLayout>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/six"
        android:id="@+id/six"
        android:layout_weight="0.25"/>
    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/five"
        android:id="@+id/five"
        android:layout_weight="0.25"/>
    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/four"
        android:id="@+id/four"
        android:layout_weight="0.25"/>
    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/minus"
        android:id="@+id/minus"
        android:layout_weight="0.25"/>

</LinearLayout>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/three"
        android:id="@+id/three"
        android:layout_weight="0.25"/>
    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/two"
        android:id="@+id/two"
        android:layout_weight="0.25"/>
    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/one"
        android:id="@+id/one"
        android:layout_weight="0.25"/>
    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/plus"
        android:id="@+id/plus"
        android:layout_weight="0.25"/>

</LinearLayout>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:layout_weight="0.5"
        android:text="@string/zero"
        android:id="@+id/zero" />
    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/dec"
        android:id="@+id/dec"
        android:layout_weight="0.25"/>
    <Button
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:text="@string/equal"
        android:id="@+id/equal"
        android:layout_weight="0.25"/>
</LinearLayout>

idclark
  • 948
  • 1
  • 8
  • 27

2 Answers2

1

You should override Fragment's onViewCreated() because it is guaranteed that all Views are instantiated.

Create a field in your CalculatorFragment to store the OnClickListener to be assigned to your buttons, you can do it like the following:

public static class CalculatorFragment extends Fragment {
    OnClickListener mListener = null;
    public CalculatorFragment(OnClickListener listener) {
        mListener = listener;
    }
    //et cetera

In the Activity's onCreate() you must specify the OnClickListener when creating the CalculatorFragment:

if (savedInstanceState == null) {
    getSupportFragmentManager().beginTransaction()
            .add(R.id.container, new CalculatorFragment(this))
            .commit();
}

In the overriden onViewCreated(), use getView() to get the View returned by onCreateView() so most calls become like getView().findViewById(R.id.the_id).setOnClickListener(this); .

See findViewById in fragment android to see how you can use findViewById

Community
  • 1
  • 1
walljam7
  • 343
  • 2
  • 11
1

The reason for the Null Pointer Exception is that the differents views (Buttons and TextView) you are trying to access are not in your activity's layout file, but in your Fragment's layout file.

setContentView adds all the views in the layout file to the activity.

To fix the NPE, you need to instantiate the views (Buttons and TextView) in your fragment's onCreateView, because the fragment's layout file contains all the views you are going to use.

public class MainActivity extends ActionBarActivity implements OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, new CalculatorFragment())
                    .commit();
        }
    }


     /**
         * A placeholder fragment containing a simple view.
         */
    public static class CalculatorFragment extends Fragment  implements OnClickListener {

        private TextView mCalculatorDisplay;

        public CalculatorFragment() {
        }

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


            mCalculatorDisplay = (TextView) rootView.findViewById(R.id.textView);

            rootView.findViewById(R.id.AC).setOnClickListener(this);
            rootView.findViewById(R.id.plusminus).setOnClickListener(this);
            rootView.findViewById(R.id.percent).setOnClickListener(this);
            rootView.findViewById(R.id.nine).setOnClickListener(this);
            rootView.findViewById(R.id.eight).setOnClickListener(this);
            rootView.findViewById(R.id.seven).setOnClickListener(this);
            rootView.findViewById(R.id.six).setOnClickListener(this);
            rootView.findViewById(R.id.five).setOnClickListener(this);
            rootView.findViewById(R.id.four).setOnClickListener(this);
            rootView.findViewById(R.id.three).setOnClickListener(this);
            rootView.findViewById(R.id.two).setOnClickListener(this);
            rootView.findViewById(R.id.one).setOnClickListener(this);
            rootView.findViewById(R.id.zero).setOnClickListener(this);
            rootView.findViewById(R.id.div).setOnClickListener(this);
            rootView.findViewById(R.id.mult).setOnClickListener(this);
            rootView.findViewById(R.id.plus).setOnClickListener(this);
            rootView.findViewById(R.id.minus).setOnClickListener(this);
            rootView.findViewById(R.id.dec).setOnClickListener(this);
            rootView.findViewById(R.id.equal).setOnClickListener(this);

            return rootView;
        }

        @Override
        public void onClick(View v) {

            String buttonPressed = ((Button )v).getText().toString();
            mCalculatorDisplay.setText(buttonPressed);
        }
    }
}

I hope this helps.

iRuth
  • 2,737
  • 3
  • 27
  • 32
  • With this I get an empty (all grey no text) screen, but the app doesn't throw an NPE. Do I need to instantiate each button individually? when i try to call `setContentView` from the fragment class, it give a compile error `non-static method cannot be called from static context` – idclark Mar 03 '15 at 12:35
  • What do you mean by `all grey no text`? Do the buttons show? Unless you want to use the button, you already instantiated it and set an `onClickListener` to it. For fragments, you only need to inflate the layout, not `setContentView`. – iRuth Mar 03 '15 at 15:44
  • 1
    I meant neither the `TextView` or `Button`s appeared. I fixed it by updating the class name to `tools:context=".MainActivity$CalculatorFragment"` in the fragment layout xml. – idclark Mar 03 '15 at 23:30