0

I am using MPAndroidCharts within a Fragment, and it is allowing me to generate the necessary tables that I want to display. Part of the MPAndroidCharts library requires the following to initialize the charts, I have this code within my Fragment .onStart() method:

barColors.add(ContextCompat.getColor(getActivity().getApplicationContext(),R.color.bar_one));
barColors.add(ContextCompat.getColor(getActivity().getApplicationContext(), R.color.bar_two));
barColors.add(ContextCompat.getColor(getActivity().getApplicationContext(), R.color.bar_three));
barColors.add(ContextCompat.getColor(getActivity().getApplicationContext(), R.color.bar_four));
barColors.add(ContextCompat.getColor(getActivity().getApplicationContext(), R.color.bar_five));

Every once in a while I am receiving the below error, and I am unsure why, it doesn't happen every time I open the Activity hosting the Fragment but does once in a while. I believe the Fragment's Context is not active yet, but I cannot confirm:

java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Context android.content.ContextWrapper.getApplicationContext()' on a null object reference at com.troychuinard.livevotingudacity.Fragment.PollFragment.updatePollResultAnswersDynamically(PollFragment.java:355) at com.troychuinard.livevotingudacity.Fragment.PollFragment.access$1300(PollFragment.java:63) at com.troychuinard.livevotingudacity.Fragment.PollFragment$3.onDataChange(PollFragment.java:304) at com.google.firebase.database.obfuscated.zzap.zza(com.google.firebase:firebase-database@@16.0.2:75) at com.google.firebase.database.obfuscated.zzca.zza(com.google.firebase:firebase-database@@16.0.2:63) at com.google.firebase.database.obfuscated.zzcd$1.run(com.google.firebase:firebase-database@@16.0.2:55) at android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6119) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

UPDATE: Please see below for relevant Fragment code:

@Override
public void onStart() {
    super.onStart();
    valueEventListener = new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {

            int numberOfPollAnswersAtIndexBelowDate = (int) dataSnapshot.child(ANSWERS_LABEL).getChildrenCount();
            updatePollResultAnswersDynamically(numberOfPollAnswersAtIndexBelowDate, dataSnapshot);

            //Simply displaying, no need for mutable transaction
            int voteCountTotal = 0;
            ArrayList<Long> pollAnswersTotals = new ArrayList<Long>();
            for (int x = 0; x < numberOfPollAnswersAtIndexBelowDate; x++) {
                pollAnswersTotals.add(x, (Long) dataSnapshot.child(ANSWERS_LABEL).child(String.valueOf(x + 1)).child(VOTE_COUNT_LABEL).getValue());
                voteCountTotal += pollAnswersTotals.get(x);
            }

            DecimalFormat formatter = new DecimalFormat("#,###,###");
            String totalFormattedVotes = formatter.format(voteCountTotal);
            mTotalVoteCounter.setText(getResources().getString(R.string.votes) + String.valueOf(totalFormattedVotes));

        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    };

    mSelectedPollRef.addValueEventListener(valueEventListener);

}

Method:

private void updatePollResultAnswersDynamically(int numberOfAnswers, DataSnapshot dataSnapshot) {

//        mPollResults.clearValues();

        pollResultChartValues = new ArrayList<BarEntry>();

        for (int i = 0; i < numberOfAnswers; i++) {
            Long chartLongResultvalue = (Long) dataSnapshot.child(ANSWERS_LABEL).child(String.valueOf(numberOfAnswers - i)).child(VOTE_COUNT_LABEL).getValue();
            float chartFloatResultvalue = chartLongResultvalue.floatValue();
            pollResultChartValues.add(new BarEntry(chartFloatResultvalue, i));
        }

        data = new BarDataSet(pollResultChartValues, "Poll Results");

//        data.setColors(new int[]{getResources().getColor(R.color.black)});
        //TODO: Check attachment to Activity; when adding a color, the getResources.getColor states
        //TODO: that the fragment was detached from the activity; potentially add this method to onCreateView() to avoid;
//        ArrayList<Integer> barColors = new ArrayList<>();
//        barColors.add(R.color.bar_one);
//        barColors.add(R.color.bar_two);
//        barColors.add(R.color.bar_three);
//        barColors.add(R.color.bar_four);
//        barColors.add(R.color.bar_five);

        mBarColors.clear();

        mBarColors.add(ContextCompat.getColor(getActivity().getApplicationContext(),R.color.bar_one));
        mBarColors.add(ContextCompat.getColor(getActivity().getApplicationContext(), R.color.bar_two));
        mBarColors.add(ContextCompat.getColor(getActivity().getApplicationContext(), R.color.bar_three));
        mBarColors.add(ContextCompat.getColor(getActivity().getApplicationContext(), R.color.bar_four));
        mBarColors.add(ContextCompat.getColor(getActivity().getApplicationContext(), R.color.bar_five));
        data.setColors(mBarColors);

        data.setAxisDependency(YAxis.AxisDependency.LEFT);
        MyDataValueFormatter f = new MyDataValueFormatter();
        data.setValueFormatter(f);


        //xAxis is a inverted yAxis since the graph is horizontal
        XAxis xAxis = mPollResults.getXAxis();
        xAxis.setDrawGridLines(false);
        xAxis.setDrawAxisLine(false);
        xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
        if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE) {
            xAxis.setTextSize(20);
        } else if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE) {
            xAxis.setTextSize(16);
        }

        //yAxis is an inverted xAxis since the graph is horizontal
        YAxis yAxisLeft = mPollResults.getAxisLeft();
        yAxisLeft.setDrawGridLines(false);
        yAxisLeft.setEnabled(false);
        YAxis yAxisRight = mPollResults.getAxisRight();
        yAxisRight.setDrawGridLines(false);
        yAxisRight.setEnabled(false);


        Legend legend = mPollResults.getLegend();
        legend.setEnabled(false);


        //TODO: This figure needs to be dynamic and needs to adjust based on the number of users in the application
        //TODO: Or does it? Right now, it scales without it

        dataSets = new ArrayList<IBarDataSet>();

        dataSets.add(data);


        //Poll Answer Options get added here
        ArrayList<String> yVals = new ArrayList<String>();
        for (int x = 0; x < numberOfAnswers; x++) {
            String pollResult = (String) dataSnapshot.child(ANSWERS_LABEL).child(String.valueOf((numberOfAnswers) - x)).child("answer").getValue();
            yVals.add(x, pollResult);
        }

        BarData testBarData = new BarData(yVals, dataSets);
        //TODO: Fix all text sizes using this method
        if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE) {
            testBarData.setValueTextSize(22);
        } else if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE) {
            testBarData.setValueTextSize(14);
        } else if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_NORMAL) {
            testBarData.setValueTextSize(12);
        } else if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_SMALL) {
            testBarData.setValueTextSize(12);
        }

        mPollResults.getXAxis().setLabelRotationAngle(-15);
        mPollResults.getXAxis().setSpaceBetweenLabels(5);
        mPollResults.setTouchEnabled(false);
        mPollResults.setPinchZoom(false);
        mPollResults.setData(testBarData);
        data.notifyDataSetChanged();
        mPollResults.notifyDataSetChanged();
        mPollResults.invalidate();
    }
tccpg288
  • 3,242
  • 5
  • 35
  • 80

2 Answers2

1

You should move all context-based initialization to onAttach(Context context) method. Fragments has to be attached to context before they can use getActivity() method. Otherwise that method will return null.

PJankowski
  • 71
  • 4
  • When I searched override methods that method was not available – tccpg288 Oct 27 '18 at 16:49
  • If you are using API < 23, you should search for `onAttach(Activity activity)`. Look at https://developer.android.com/reference/android/support/v4/app/Fragment.html#onAttach(android.app.Activity) or https://developer.android.com/reference/android/support/v4/app/Fragment.html#onAttach(android.app.Activity) if you are not using support library. – PJankowski Oct 27 '18 at 16:55
  • I just realized, that I pasted the same link twice. If you are not using support library, you can check that method in here: https://developer.android.com/reference/android/app/Fragment.html#onAttach(android.app.Activity) – PJankowski Oct 27 '18 at 17:35
1

You are dealing with two different component lifecycles that don't know about one another:

  1. The lifecycle of your fragment which does not always have an attached context (Activity)
  2. The lifecycle of your Firebase ValueEventListener which is tied to reads from your database.

The way things are written, neither your fragment nor your ValueEventListener know about the other's lifecycle.

A couple of ways you could address this:

Remove your ValueEventListener in onStop

Since onStart happens after onAttach and onStop happens before onDetach, you could remove your ValueEventListener in onStop. That way, your listener registration would be symmetric. Of course, you'll miss DataSnapshots while your fragment is detached from a Context. If that's OK, this will probably work fine.

Pass an application context to your Fragment constructor

Another approach would be to have your Fragment take a context in it's constructor and hold onto it in a member variable and reference it instead of calling getActivity().

NOTE: If you do pass a Context in the constructor, be sure to get the application context off of the passed in Context and reference that to avoid leaking an activity context. You can do this by calling context.getApplicationContext();

Michael Krause
  • 4,689
  • 1
  • 21
  • 25
  • 1
    Wow. The Firebase recommended approach is to detach in onStop(), which I actually knew but have completely neglected. Thanks for the help! – tccpg288 Oct 27 '18 at 19:56