0

I am trying to create an inverse of a lap counter. Each timer display (hh:mm:ss) is a linear layout made of edittexts. I have a counter (for lap counts) which is a linear layout with just 1 editText. I have a fragment which shows 2 timer layouts and 1 counter layout.

My motive is to let the user enter the total time and one of the two values : number of laps / time per lap. Then I want to calculate one from the other using the following basic equation... timePerLap = totalTime / lapCount. I have two radiobuttons which control the visibility of only one field at a time.

Now the question : I want to do the calculation and update the value on onCheckedChanged() of the radiobuttons depending on the user entered value. I call a method inside the custom layout class to set the value using setText of the EditText but it throws an NPE with the following stackTrace. And I debugged and found that the editText is NOT NULL. So that isn't the cause of the problem.

java.lang.NullPointerException
            at android.widget.TextView.sendBeforeTextChanged(TextView.java:8850)
            at android.widget.TextView.access$1100(TextView.java:282)
            at android.widget.TextView$ChangeWatcher.beforeTextChanged(TextView.java:11243)
            at android.text.SpannableStringBuilder.sendBeforeTextChanged(SpannableStringBuilder.java:956)
            at android.text.SpannableStringBuilder.replace(SpannableStringBuilder.java:466)
            at android.text.SpannableStringBuilder.delete(SpannableStringBuilder.java:212)
            at android.text.SpannableStringBuilder.delete(SpannableStringBuilder.java:30)
            at android.text.method.BaseKeyListener.backspaceOrForwardDelete(BaseKeyListener.java:94)
            at android.text.method.BaseKeyListener.backspace(BaseKeyListener.java:49)
            at android.text.method.BaseKeyListener.onKeyDown(BaseKeyListener.java:155)
            at android.text.method.NumberKeyListener.onKeyDown(NumberKeyListener.java:138)
            at android.widget.TextView.doKeyDown(TextView.java:6907)
            at android.widget.TextView.onKeyDown(TextView.java:6679)
            at android.view.KeyEvent.dispatch(KeyEvent.java:3173)
            at android.view.View.dispatchKeyEvent(View.java:7994)
            at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1470)
            at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1470)
            at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1470)
            at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1470)
            at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1470)
            at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1470)
            at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1470)
            at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1470)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:2262)
            at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1612)
            at android.app.Activity.dispatchKeyEvent(Activity.java:2524)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:2181)
            at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:4665)
            at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4632)
            at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4197)
            at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4251)
            at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4220)
            at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4331)
            at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4228)
            at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4388)
            at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4197)
            at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4251)
            at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4220)
            at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4228)
            at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4197)
            at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6564)
            at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6481)
            at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6452)
            at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6417)
            at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:3764)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:146)
            at android.app.ActivityThread.main(ActivityThread.java:5487)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1283)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
            at dalvik.system.NativeStart.main(Native Method)

Main fragment :

public class WorkoutTimerFragment extends Fragment {
    TimerLayout mTotalWOTime;
    TimerLayout mTimePerRep;
    RadioButton mTimePerRepRadio;
    RadioButton mRepCountRadio;
    Counter mRepCount;

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

        //All variables are initialized ...

        mTimePerRep = (TimerLayout) timePerRepLayout.getChildAt(0);
        mTimePerRep.setVisibility(View.INVISIBLE);
        mRepCount = (Counter) timePerRepLayout.getChildAt(1);

        RadioGroup radioGroupLayout = (RadioGroup) repLayout.getChildAt(1);
        mTimePerRepRadio = (RadioButton) radioGroupLayout.getChildAt(0);
        mRepCountRadio = (RadioButton) radioGroupLayout.getChildAt(1);
        mRepCountRadio.setOnCheckedChangeListener(null);
        mRepCountRadio.setChecked(true);

        mTimePerRepRadio.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked) {
                    mTimePerRep.setVisibility(View.VISIBLE);
                    mRepCount.setVisibility(View.INVISIBLE);
                    calculateStartTimes();
                }
            }
        });

        mRepCountRadio.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked) {
                    mTimePerRep.setVisibility(View.INVISIBLE);
                    mRepCount.setVisibility(View.VISIBLE);
                }
                calculateStartTimes();
            }
        });

        return parentLayout;
    }

    public void calculateStartTimes() {
        long totalWorkoutTime = mTotalWOTime.getTotalMilliseconds();
        long timePerRep;
        if (totalWorkoutTime > 0) {
            if (mRepCount.getCounterValue() > 0) {
                mTimePerRep.setTime(totalWorkoutTime / mRepCount.getCounterValue());
            } else if ((timePerRep = mTimePerRep.getTotalMilliseconds()) > 0) {
                mRepCount.setCounterValue((int) (totalWorkoutTime / timePerRep));
            } else {
                //TODO AlertDialog showing the user needs to enter some information
            }
        }
    }
}

Counter.java - Layout that is causing the below problem in the setCounterValue() method on line mCounter.setText(String.valueOf(counterValue));

public class Counter extends LinearLayout implements BaseLayout {
    private final TextView mCounterHeader;
    private final EditText mCounter;
    private TextWatcher mTextWatcher;
    private BaseLayout.OnItemChangeListener mItemChangedListener;
    private View mView;

    public Counter(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Options, 0, 0);
        String titleText = a.getString(R.styleable.Options_titleText);

        setOrientation(LinearLayout.VERTICAL);
        setGravity(Gravity.CENTER_VERTICAL);

        LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mView = inflater.inflate(R.layout.counter, this, true);

        mCounterHeader = (TextView) getChildAt(0);
        mCounterHeader.setText(titleText);

        mCounter = (EditText) getChildAt(1);
        mCounter.setText("0");
        mCounter.addTextChangedListener(mTextWatcher);

        mTextWatcher = new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence value, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence value, int start, int before, int count) {
            //Some Listener call back I want to implement but don't have an implementation for yet. 
                if (mItemChangedListener != null) {
                    mItemChangedListener.onItemChanged(Counter.this);
                }
            }

            @Override
            public void afterTextChanged(Editable editableValue) {
            }
        };
    }

    public int getCounterValue() {
        return Integer.parseInt((mCounter.getText().toString()));
    }

    public void setCounterValue(int counterValue) {
        if (counterValue > 0) {
            mCounter.setText(String.valueOf(counterValue));
        }
    }

    public void setItemChangedListener(BaseLayout.OnItemChangeListener itemChangedListener) {
        mItemChangedListener = itemChangedListener;
    }
}
LeoNeo
  • 739
  • 1
  • 9
  • 28
  • Share the code with us what you have done – Fahim Feb 23 '15 at 01:50
  • added the relevant code. Though will adding this code I ended up deleting some code in the effort to not share useless code and discovered that the problem is around adding the textWatcher in Counter.java. The moment I commented out the line mCounter.addTextChangedListener(mTextWatcher); and the whole chunk of code around setting the mTextWatcher, the application seemed to work fine. My initial issue is resolved but I am still puzzled as to why the textWatcher should interfere with setText and cause a NPE. – LeoNeo Feb 23 '15 at 02:23

1 Answers1

0

From your above code, it seems that you have not intialised the custom listener byt calling

setItemChangedListener in your activity. Please confirm this

Fahim
  • 12,198
  • 5
  • 39
  • 57
  • u need to initialise it or else it will throw an exception – Fahim Feb 23 '15 at 02:47
  • why? i have surrounded the call to the onItemChanged method with a null check on the listener. I can always set a listener to null. – LeoNeo Feb 23 '15 at 02:56