javaandroidmpandroidchart

MPAndroidChart Live Plot Crashing


I'm trying to create a sample Android application using MPAndroidPlot to live-plot some random data. I'm attempting to make the application behave as follows:

  1. A thread calls addEntry, sleeps for a period of time, and repeats
  2. addEntry appends the entry. If the number of entries is above a predetermined maximum number of entries, it will delete the first entry and shift all other entries to the left by one unit before appending the new entry.

Here is the code, below, I will explain the problem.

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "SCOPEN";

    private static final int MAX_SAMPLES = 100;
    private static final int BG_COLOR = 0xFF47474E;
    private static final int LINE_COLOR = 0xFFFFFACD;

    private float mTimeStep = 0.5f;
    private float mMaxY = 10;
    private float mMinY = 0;

    private LineChart mChart;
    private LineDataSet mDataSet;
    private LineData mLineData;

    @SuppressLint("SourceLockedOrientationActivity")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        setContentView(R.layout.activity_main);

        // Configure graph
        mChart = (LineChart) findViewById(R.id.chart);
        mChart.setHardwareAccelerationEnabled(true);
        mChart.setBackgroundColor(BG_COLOR);
        mChart.setDescription(null);
        mChart.setAutoScaleMinMaxEnabled(false);
        mChart.setPinchZoom(false);
        mChart.setDragEnabled(true);
        mChart.setScaleEnabled(true);
        mChart.setTouchEnabled(false);

        // Disable legend
        Legend legend = mChart.getLegend();
        legend.setEnabled(false);

        // Configure y-axis right
        mChart.getAxisRight().setEnabled(false);

        // Configure y-axis left
        YAxis yAxisLeft = mChart.getAxisLeft();
        yAxisLeft.setDrawGridLines(true);
        yAxisLeft.setDrawAxisLine(false);
        yAxisLeft.setDrawLabels(false);
        yAxisLeft.setAxisMinimum(mMinY);
        yAxisLeft.setAxisMaximum(mMaxY);
        //yAxisLeft.enableGridDashedLine(10, 0, 0);

        // Configure x-axis
        XAxis xAxis = mChart.getXAxis();
        xAxis.setDrawGridLines(true);
        xAxis.setDrawAxisLine(false);
        xAxis.setDrawLabels(false);
        xAxis.setAxisMinimum(0);
        xAxis.setAxisMaximum(mTimeStep * (MAX_SAMPLES - 1));
        //xAxis.enableGridDashedLine(10, 10, 0);

        // Configure data
        mDataSet = new LineDataSet(null, null);
        mDataSet.setDrawValues(false);
        mDataSet.setColor(LINE_COLOR);
        mDataSet.setCircleColor(LINE_COLOR);
        mDataSet.setLineWidth(3f);
        mDataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER);

        mLineData = new LineData(mDataSet);
        mChart.setData(mLineData);
        mChart.invalidate();

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    addEntry(new Random().nextInt(10));
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        thread.start();
    }

    public void addEntry(final float v) {
        if (mDataSet.getEntryCount() == MAX_SAMPLES) {
            mDataSet.removeFirst();
            for (Entry entry : mDataSet.getValues()) {
                entry.setX(entry.getX() - mTimeStep);
            }
        }
        mDataSet.addEntry(new Entry(mDataSet.getEntryCount() * mTimeStep, v));
        mDataSet.notifyDataSetChanged();
        mLineData.notifyDataChanged();
        mChart.notifyDataSetChanged();
        mChart.invalidate();
    }
}

The problem: my application crashes after a random period of time. Decreasing the time the entry-spawning thread sleeps accelerates the time until crash (< 50ms typically leads to a consistent crash within 10 seconds).

The error is:

2020-05-25 21:58:32.241 18392-18392/com.example.scopen E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.scopen, PID: 18392
    java.lang.IndexOutOfBoundsException: Index: 99, Size: 100
        at java.util.ArrayList.get(ArrayList.java:437)
        at com.github.mikephil.charting.data.DataSet.getEntryForIndex(DataSet.java:294)
        at com.github.mikephil.charting.renderer.LineChartRenderer.drawCircles(LineChartRenderer.java:667)
        at com.github.mikephil.charting.renderer.LineChartRenderer.drawExtras(LineChartRenderer.java:601)
        at com.github.mikephil.charting.charts.BarLineChartBase.onDraw(BarLineChartBase.java:255)
        at android.view.View.draw(View.java:23190)
        at android.view.View.updateDisplayListIfDirty(View.java:22065)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
        at android.view.View.updateDisplayListIfDirty(View.java:22020)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
        at android.view.View.updateDisplayListIfDirty(View.java:22020)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
        at android.view.View.updateDisplayListIfDirty(View.java:22020)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
        at android.view.View.updateDisplayListIfDirty(View.java:22020)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
        at android.view.View.updateDisplayListIfDirty(View.java:22020)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
        at android.view.View.updateDisplayListIfDirty(View.java:22020)
        at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:588)
        at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:594)
        at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:667)
        at android.view.ViewRootImpl.draw(ViewRootImpl.java:4263)
        at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4047)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3320)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2200)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9065)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:999)
        at android.view.Choreographer.doCallbacks(Choreographer.java:797)
        at android.view.Choreographer.doFrame(Choreographer.java:732)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:984)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:237)
        at android.app.ActivityThread.main(ActivityThread.java:8016)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1076)

Notice that I get an IndexOutOfBoundsException where the attempted index access is less than the size of the ArrayList. I've tried with several different maximum sizes and the index is always size - 1. I suspect there is some sort of concurrency problem. Perhaps when mChart.invalidate() is called, it triggers the screen to refresh. Then, there is the possibility of a state where the thread deletes a value just as the display attempts to read the last value.

I'm not java or Android expert, so I'd appreciate any help. Thanks!


Solution

  • I also have this issues. I find the problem that Exception is very clear that you have tried to access some index which is not available in the list. I have look into the MainActivity.java I have found the following glitch. You are trying to fetch data from a list without verifying the list(whether null or empty ).

    https://www.codeproject.com/Questions/987512/How-to-fix-Unable-to-start-activity-ComponentInfo