3

Goal:

To create a helper class for graph generation

Background:

I have 3 fragments that each collect some sensor data (accelerometer, gyroscope, rotation) and plots a graph using GraphView. Here is what the code looks like for one of those fragments (this code currently works correctly):

public class GyroscopeFragment extends Fragment implements SensorEventListener {

    private final short TYPE_GYROSCOPE = Sensor.TYPE_GYROSCOPE;
    private final short POLL_FREQUENCY = 100; //in milliseconds
    private final short MAX_POINTS_DISPLAYED = 50;
    private SensorManager sensorManager;
    private Sensor sensor;
    private Sensor gyroscope;
    private long lastUpdate = -1;

    float curGyroX;
    float curGyroY;
    float curGyroZ;

    private LineGraphSeries<DataPoint> gyroXSeries;
    private LineGraphSeries<DataPoint> gyroYSeries;
    private LineGraphSeries<DataPoint> gyroZSeries;
    private double graphXTime = 1d;

    public GyroscopeFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_gyroscope, container, false);

        //Set the nav drawer item highlight
        MainActivity mainActivity = (MainActivity) getActivity();
        mainActivity.navigationView.setCheckedItem(R.id.nav_gyroscope);

        //Set actionbar title
        mainActivity.setTitle("Gyroscope");

        //Sensor manager
        sensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
        gyroscope = (Sensor) sensorManager.getDefaultSensor(TYPE_GYROSCOPE);

        return view;
    }


    @Override
    public void onResume() {
        super.onResume();
        sensorManager.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_FASTEST);

        //Create graph
        GraphView gyroGraph = (GraphView) getView().findViewById(R.id.gyroGraph);

        gyroGraph.getLegendRenderer().setVisible(true);
        gyroGraph.getLegendRenderer().setFixedPosition(0,0);

        gyroGraph.getGridLabelRenderer().setHighlightZeroLines(false);

        gyroXSeries = new LineGraphSeries<DataPoint>();
        gyroYSeries = new LineGraphSeries<DataPoint>();
        gyroZSeries = new LineGraphSeries<DataPoint>();

        gyroXSeries.setTitle("X");
        gyroYSeries.setTitle("Y");
        gyroZSeries.setTitle("Z");

        gyroXSeries.setColor(Color.RED);
        gyroYSeries.setColor(Color.GREEN);
        gyroZSeries.setColor(Color.BLUE);

        gyroGraph.addSeries(gyroXSeries);
        gyroGraph.addSeries(gyroYSeries);
        gyroGraph.addSeries(gyroZSeries);

        gyroGraph.getViewport().setYAxisBoundsManual(true);
        gyroGraph.getViewport().setMinY(-10);
        gyroGraph.getViewport().setMaxY(10);

        gyroGraph.getViewport().setXAxisBoundsManual(true);
        gyroGraph.getViewport().setMinX(0);
        gyroGraph.getViewport().setMaxX(MAX_POINTS_DISPLAYED);
    }

    @Override
    public void onPause() {
        super.onPause();
        sensorManager.unregisterListener(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        sensorManager.unregisterListener(this);
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        sensorManager.unregisterListener(this);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        sensor = event.sensor;

        curGyroX = event.values[0];
        curGyroY = event.values[1];
        curGyroZ = event.values[2];

        long curTime = System.currentTimeMillis();
        long diffTime = (curTime - lastUpdate);

        // only allow one update every POLL_FREQUENCY.
        if (diffTime > POLL_FREQUENCY) {
            lastUpdate = curTime;

            graphXTime += 1d;
            gyroXSeries.appendData(new DataPoint(graphXTime, curGyroX), true, MAX_POINTS_DISPLAYED);
            gyroYSeries.appendData(new DataPoint(graphXTime, curGyroY), true, MAX_POINTS_DISPLAYED);
            gyroZSeries.appendData(new DataPoint(graphXTime, curGyroZ), true, MAX_POINTS_DISPLAYED);
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        //Safe not to implement
    }
}

I find that across the 3 fragments I am repeating a lot of the graph generation code in onResume so I wanted to create a GraphHelper class that I can instantiate to do the graph creation, with the benefit of only having to change code in one place if I wanted to alter the look of a graph. However, I am new to Java so having trouble wrapping my head around creating this new class.

Attempted solution:

I have managed to come up with the following class in an attempt to solve the problem:

public class GraphHelper {

    private final GraphView graph;
    private final LineGraphSeries<DataPoint> xSeries;
    private final LineGraphSeries<DataPoint> ySeries;
    private final LineGraphSeries<DataPoint> zSeries;

    public GraphHelper(GraphView graph, LineGraphSeries<DataPoint> xSeries,
                            LineGraphSeries<DataPoint> ySeries, LineGraphSeries<DataPoint> zSeries, short maxDisplayed){
        this.graph = graph;
        this.xSeries = xSeries;
        this.ySeries = ySeries;
        this.zSeries = zSeries;

        graph.getLegendRenderer().setVisible(true);
        graph.getLegendRenderer().setFixedPosition(0, 0);

        graph.getGridLabelRenderer().setHighlightZeroLines(false);

        xSeries = new LineGraphSeries<DataPoint>();
        ySeries = new LineGraphSeries<DataPoint>();
        zSeries = new LineGraphSeries<DataPoint>();

        xSeries.setTitle("X");
        ySeries.setTitle("Y");
        zSeries.setTitle("Z");

        xSeries.setColor(Color.RED);
        ySeries.setColor(Color.GREEN);
        zSeries.setColor(Color.BLUE);

        graph.addSeries(xSeries);
        graph.addSeries(ySeries);
        graph.addSeries(zSeries);

        graph.getViewport().setYAxisBoundsManual(true);
        graph.getViewport().setMinY(-10);
        graph.getViewport().setMaxY(10);

        graph.getViewport().setXAxisBoundsManual(true);
        graph.getViewport().setMinX(0);
        graph.getViewport().setMaxX(maxDisplayed);
    }
}

And have changed my fragment onResume to look like this (everything else is the same):

@Override
public void onResume() {
    super.onResume();
    sensorManager.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_FASTEST);

    //Create graph
    GraphView gyroGraph = (GraphView) getView().findViewById(R.id.gyroGraph);

    new GraphHelper(gyroGraph, gyroXSeries, gyroYSeries, gyroZSeries, MAX_POINTS_DISPLAYED);
}

Problem #1:

I am getting the following error whenever I try to view the graph fragment:

java.lang.NullPointerException: Attempt to invoke virtual method 'void com.jjoe64.graphview.series.LineGraphSeries.appendData(com.jjoe64.graphview.series.DataPointInterface, boolean, int)' on a null object reference

This is referring to the onSensorChanged method where I'm trying to append data to the series. It doesn't seem to know what gyroXSeries is because I'm adding it to the graph within the helper.

Problem #2:

Currently I'm declaring gyroXSeries in the main fragment class, as well as the top of the helper class, and also creating a new LineGraphSeries object in the helpers constructor. Is there a way to reduce the repetition of this code?

EDIT

In response to the comments below, I made the following changes and the new GraphHelper class looks like this:

public class GraphHelper {

    private final GraphView graph;
    private final LineGraphSeries<DataPoint> xSeries;
    private final LineGraphSeries<DataPoint> ySeries;
    private final LineGraphSeries<DataPoint> zSeries;


    public GraphHelper(GraphView graph, LineGraphSeries<DataPoint> xSeries,
                            LineGraphSeries<DataPoint> ySeries, LineGraphSeries<DataPoint> zSeries, short maxDisplayed){
        this.graph = graph;
        this.xSeries = xSeries;
        this.ySeries = ySeries;
        this.zSeries = zSeries;

        graph.getLegendRenderer().setVisible(true);
        graph.getLegendRenderer().setFixedPosition(0, 0);

        graph.getGridLabelRenderer().setHighlightZeroLines(false);

        xSeries.setTitle("X");
        ySeries.setTitle("Y");
        zSeries.setTitle("Z");

        xSeries.setColor(Color.RED);
        ySeries.setColor(Color.GREEN);
        zSeries.setColor(Color.BLUE);

        graph.addSeries(xSeries);
        graph.addSeries(ySeries);
        graph.addSeries(zSeries);

        graph.getViewport().setYAxisBoundsManual(true);
        graph.getViewport().setMinY(-10);
        graph.getViewport().setMaxY(10);

        graph.getViewport().setXAxisBoundsManual(true);
        graph.getViewport().setMinX(0);
        graph.getViewport().setMaxX(maxDisplayed);
    }
}

The gyroscope fragment is the same as the original, with the change to onResume that I made above

Which results in the following error:

FATAL EXCEPTION: main Process: net.binarysea.sensorload, PID: 25997 java.lang.NullPointerException: Attempt to invoke virtual method 'void com.jjoe64.graphview.series.LineGraphSeries.setTitle(java.lang.String)' on a null object reference at net.binarysea.sensorload.GraphHelper.(GraphHelper.java:30) at net.binarysea.sensorload.GyroscopeFragment.onResume(GyroscopeFragment.java:73) at android.support.v4.app.Fragment.performResume(Fragment.java:2005) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1108) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1248) at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:738) at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1613) at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:517) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:5417) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

Simon
  • 9,762
  • 15
  • 62
  • 119
  • For problem 1 I would make sure that all the arguments are not null to start with, It doesn't look like you helper class has anything inherently wrong with it. to get an idea as to where that error is happening you should see a line in the console that's a few lines down from where that nullpointer exception occurred that will tell you the line number at which it failed. – Austi01101110 Feb 29 '16 at 18:53
  • To fix problem 2, instead of declaring a new gyroXSeries just point to the existing series IE: this.xSeries = xSeries; (technically those could be converted to local variables since you are only using them in the constructor) – Austi01101110 Feb 29 '16 at 18:57
  • it is pointing to line 110 of the main fragment class, which is where I'm doing `gyroXSeries.appendData(new DataPoint(graphXTime, curGyroX), true, MAX_POINTS_DISPLAYED)`, but I'm not sure why `gyroXSeries` would be null because I declare it at the top, and add data to in `onResume` through the helper class, so by the time the `onSensorChanged` code executes it should have something in it – Simon Feb 29 '16 at 18:59
  • The biggest error I see is that you are redeclaring the passed in xSeries and not initializing the private final xSeries. I would start there. (to fix that just do this.xSeries = new LineGraphSeries(); or have them point to the existing series by: this.xSeries = xSeries;) – Austi01101110 Feb 29 '16 at 19:04
  • So I've tried both ways. I already have `this.xSeries = xSeries;` so I removed the lines that say `xSeries = new LineGraphSeries();`, this gives me a NPE when I try to `xSeries.setTitle("X");`. I have also tried using `this.xSeries = new LineGraphSeries();` and removing the lines that say `this.xSeries = xSeries;` but that gives me the same NPE – Simon Feb 29 '16 at 19:56
  • that would indicate to me that you are passing in a null LineGraphSeries. Can you add the full error printout & the new Gyroscope fragment. If i were to take a stab in the dark i would say that you are referencing the gyroXSeries in the Gyroscope fragment not in the helper class. – Austi01101110 Feb 29 '16 at 20:28
  • @Austi01101110 see my edits above. the gyroscope fragment has not been changed, only the helper class – Simon Feb 29 '16 at 20:59

1 Answers1

2

Change your graph helper class to this.

public class GraphHelper {
    public GraphHelper(GraphView graph, LineGraphSeries<DataPoint> xSeries,
                   LineGraphSeries<DataPoint> ySeries, LineGraphSeries<DataPoint> zSeries, short maxDisplayed){

    graph.getLegendRenderer().setVisible(true);
    graph.getLegendRenderer().setFixedPosition(0, 0);

    graph.getGridLabelRenderer().setHighlightZeroLines(false);

    xSeries.setTitle("X");
    ySeries.setTitle("Y");
    zSeries.setTitle("Z");

    xSeries.setColor(Color.RED);
    ySeries.setColor(Color.GREEN);
    zSeries.setColor(Color.BLUE);

    graph.addSeries(xSeries);
    graph.addSeries(ySeries);
    graph.addSeries(zSeries);

    graph.getViewport().setYAxisBoundsManual(true);
    graph.getViewport().setMinY(-10);
    graph.getViewport().setMaxY(10);

    graph.getViewport().setXAxisBoundsManual(true);
    graph.getViewport().setMinX(0);
    graph.getViewport().setMaxX(maxDisplayed);
    }
}

Call it like this:

@Override
public void onResume() {
    super.onResume();
    sensorManager.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_FASTEST);

    //Create graph
    GraphView gyroGraph = (GraphView) getView().findViewById(R.id.gyroGraph);

    gyroXSeries = new LineGraphSeries<DataPoint>();
    gyroYSeries = new LineGraphSeries<DataPoint>();
    gyroZSeries = new LineGraphSeries<DataPoint>();

    new GraphHelper(gyroGraph, gyroXSeries, gyroYSeries, gyroZSeries, MAX_POINTS_DISPLAYED);
}

Also this is a more proper way to do a helper in java:

public class GraphHelper {

public GraphHelper() { }

public static void initializeGraph(GraphView graph, LineGraphSeries<DataPoint> xSeries,
                                   LineGraphSeries<DataPoint> ySeries, LineGraphSeries<DataPoint> zSeries, short maxDisplayed){
    graph.getLegendRenderer().setVisible(true);
    graph.getLegendRenderer().setFixedPosition(0, 0);

    graph.getGridLabelRenderer().setHighlightZeroLines(false);

    xSeries.setTitle("X");
    ySeries.setTitle("Y");
    zSeries.setTitle("Z");

    xSeries.setColor(Color.RED);
    ySeries.setColor(Color.GREEN);
    zSeries.setColor(Color.BLUE);

    graph.addSeries(xSeries);
    graph.addSeries(ySeries);
    graph.addSeries(zSeries);

    graph.getViewport().setYAxisBoundsManual(true);
    graph.getViewport().setMinY(-10);
    graph.getViewport().setMaxY(10);

    graph.getViewport().setXAxisBoundsManual(true);
    graph.getViewport().setMinX(0);
    graph.getViewport().setMaxX(maxDisplayed);
}
}

This way you don't have a random object that's waiting to be garbage collected. To call it use:

GraphHelper.initializeGraph(...)
Austi01101110
  • 616
  • 4
  • 23
  • I'm getting an android studio error saying it cannot resolve symbol initializeGraph, do I need to add something to my imports? – Simon Feb 29 '16 at 21:18
  • Nevermind, I accidentally left the `new` keyword in there. Works now! – Simon Feb 29 '16 at 21:21