Hot questions for Handling nullpointerexception in MPAndroidChart

Top 10 Java Open Source / MPAndroidChart / Handling nullpointerexception

How to prevent java.lang.nullpointerexception on adding user input to MPandroidchart

Question: I've created a line chart in a fragment on android studio using MPandroidchart, and made it so that the points of the chart are from user input. However, I get the error 'java.lang.NullPointerException'. I understand that this error is caused by attempting to use a value that is null, but I'm not sure how to fix it in my case.

I retrieve the user input from a method where there is code to ensure that the input is not saved if it is null. I believe the issue is because the graph is attempting to build before the user can add the data, as the input option is part of the fragment where the graph is displayed, so the graph is trying to build before the data is ready.

Graph.java

public class Graph extends Fragment {

    public Graph() {
        // Required empty public constructor
    }
    private LineChart mChart;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_graph, container, false);
        //initialize input1 database
        InputRoomDatabase db = Room.databaseBuilder(getActivity(),InputRoomDatabase.class, "input_database")
                .build();
        //initialize chart
        mChart = view.findViewById(R.id.chart);
        //set description
        Description description = new Description();
        description.setText("Blood glucose levels");
        mChart.setDescription(description);
            //set description if no data is available
        mChart.setNoDataText("No Data Available. Add a blood glucose level input to see your graph");
        //enable touch gestures
        mChart.setTouchEnabled(true);
        //enable scaling and dragging
        mChart.setDragEnabled(true);
        mChart.setScaleEnabled(true);
        //draw background grid
        mChart.setDrawGridBackground(true);
        //draw x-axis
        XAxis xAxis = mChart.getXAxis();
        xAxis.setEnabled(false);
        // setting position to TOP and INSIDE the chart
        xAxis.setPosition(XAxis.XAxisPosition.TOP_INSIDE);
        //  setting text size for our axis label
        xAxis.setTextSize(10f);
        xAxis.setTextColor(Color.BLACK);
        // to draw axis line
        xAxis.setDrawAxisLine(true);
        xAxis.setDrawGridLines(false);

        YAxis yAxis = mChart.getAxisLeft();
        // setting the count of Y-axis label's
        yAxis.setLabelCount(12, false);
        yAxis.setTextColor(Color.BLACK);
        yAxis.setPosition(YAxis.YAxisLabelPosition.OUTSIDE_CHART);
        yAxis.setDrawGridLines(true);

        mChart.getAxisRight().setEnabled(false);
        // add data
        setData();
        mChart.getLegend().setEnabled(false);
        // refresh the chart

        mChart.notifyDataSetChanged();
        mChart.invalidate();


        return view;
    }

    private void setData(){
        ArrayList<Entry> values = new ArrayList<>();
**
        Double d = getActivity().getIntent().getExtras().getDouble("STRING_I_NEED");
**
        float myDataSet = d.floatValue();

        values.add(new Entry(1,myDataSet));

        LineDataSet set1;
        if (mChart.getData() != null &&
                mChart.getData().getDataSetCount() > 0) {
            set1 = (LineDataSet) mChart.getData().getDataSetByIndex(0);
            set1.setValues(values);
            mChart.getData().notifyDataChanged();
            mChart.notifyDataSetChanged();
        } else {
            set1 = new LineDataSet(values, "Blood Glucose Levels");
            set1.setDrawIcons(false);
            set1.setLineWidth(2f);
            set1.setCircleRadius(3f);
            set1.setDrawCircleHole(false);
            set1.setValueTextSize(9f);
            set1.setFormLineWidth(1f);
            set1.setFormSize(15.f);
            set1.setColor(Color.BLACK);
            set1.setCircleColor(Color.BLACK);

        }
        ArrayList<ILineDataSet> dataSets = new ArrayList<>();
        dataSets.add(set1);
        LineData data = new LineData(dataSets);
        mChart.setData(data);

    }
}

Tabbed.java (base class of the fragment) (relevant code only)

//getting inputs from room and putting them into the view in table
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        mInputViewModel = ViewModelProviders.of(this).get(InputViewModel.class);
        //only add if there is a new input
        if (requestCode == INPUT_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK) {
            //retrieve input from room database
            Input1 input1 = new Input1(data.getStringExtra(input.EXTRA_REPLY));
            //insert the inputs into the view model of the recyclerView
            mInputViewModel.insert(input1);
            //convert the String input1 to a double
            Double d = Double.parseDouble(input1.getValue());
            //display pop-up if blood is above target
            if (d > 8.0) {
                Toast.makeText(
                        getApplicationContext(),
                        R.string.PopupHigh,
                        Toast.LENGTH_LONG).show();
            }
            //display pop-up if blood is below target
            if (d < 4.0) {
                Toast.makeText(
                        getApplicationContext(),
                        R.string.PopupLow,
                        Toast.LENGTH_LONG).show();
            }
            Intent i = new Intent(Tabbed.this, Graph.class);
            i.putExtra("STRING_I_NEED", d);
            //display pop-up if no input is added don't save the input
        } else {
            Toast.makeText(
                    getApplicationContext(),
                    R.string.empty_not_saved,
                    Toast.LENGTH_LONG).show();
        }
    }
        public static final int INPUT_ACTIVITY_REQUEST_CODE = 1;

}

When I run the app, it crashes and gives this error "java.lang.NullPointerException: Attempt to invoke virtual method 'double android.os.Bundle.getDouble(java.lang.String)' on a null object reference" I've marked the problem code in Graph.java with **

As I said, I believe the issue is that the graph is trying to build before the data is ready, but I'm not sure how to avoid this issue.

Answer: The code:

Double d = getActivity().getIntent().getExtras().getDouble("STRING_I_NEED");
float myDataSet = d.floatValue();

assumes 4 method calls all return non-null values. Please see the documentation for library methods to find out when they might return null, and in general how to avoid unhappy surprises.

The documentation says getExtras() returns: the map of all extras previously added with putExtra(), or null if none have been added.

When calling a library method that can return null, your code should check for the null result (or catch the NullPointerException), or ensure that that case can't happen, or both.

  1. Since Intents come in from the rest of the system, you can't guarantee that they will always have extras, so the code will need to test if the result of getExtras() is null before calling getDouble(), and handle that gracefully.
  2. You'll also need to debug why this Intents has no extras when you expected that it would.
  3. If the Intent has extras but those extras don't have a double value called "STRING_I_NEED", then getDouble("STRING_I_NEED") will return 0.0. You might need to pass an explicit defaultValue to getDouble() or test if the Bundle containsKey("STRING_I_NEED").
  4. If you want a float value, consider calling getFloat() instead of getDouble(). I think that will convert a double value to float for you.