Search code examples
androidbutterknife

Injecting view in a listener not working with Butterknife


To avoid "inner class hell" in case of Android event listeners, I have moved the listeners to separate classes. Following is one of such listeners for a TextView which holds a date string. On touching it, I open a DatePickerDialog and set the selected date value back to the TextView.

I further enhanced this listener to use Butterknife as follows:

public class DateViewClickListener implements View.OnClickListener {

    private final DateTime prevDate;
    private DateTimeFormatter dateFmt;

    public DateViewClickListener(DateTime prevDate, DateTimeFormatter dateFmt) {
        this.prevDate = prevDate;
        this.dateFmt = dateFmt;
    }

    @Override
    public void onClick(View view) {
        new DatePickerDialog(view.getContext(), new DatePickerDialog.OnDateSetListener() {

            @InjectView(R.id.date)
            TextView dateView;

            @Override
            public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
                Activity activity = ActivityUtil.getParentActivity(view);
                ButterKnife.inject(this, activity.getWindow().getDecorView());
                DateTime newDate = prevDate.withDate(year, monthOfYear, dayOfMonth);
                dateView.setText(dateFmt.print(newDate));
            }
        }, prevDate.getYear(), prevDate.getMonthOfYear(), prevDate.getDayOfMonth()).show();
    }
}

ActivityUtil.getParentActivity(view) used in the above code, scans through the context hierarchy of the view and finds its parent activity. Following is the code for it:

public class ActivityUtil {

    public static Activity getParentActivity(View view) {
        Context context = view.getContext();
        return scanForActivity(context);
    }

    private static Activity scanForActivity(Context context) {
        if (context == null)
            return null;
        else if (context instanceof Activity)
            return (Activity) context;
        else if (context instanceof ContextWrapper)
            return scanForActivity(((ContextWrapper) context).getBaseContext());
        return null;
    }
}

On executing this code, the dateView remains null throwing an NPE. However, this code works for me when I don't use Butterknife (the DateViewClickListener class is as follows in that case).

public class DateViewClickListener implements View.OnClickListener {

    private final DateTime prevDate;
    private DateTimeFormatter dateFmt;

    public DateViewClickListener(DateTime prevDate, DateTimeFormatter dateFmt) {
        this.prevDate = prevDate;
        this.dateFmt = dateFmt;
    }

    @Override
    public void onClick(View view) {
        new DatePickerDialog(view.getContext(), new DatePickerDialog.OnDateSetListener() {

            @Override
            public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
                Activity activity = ActivityUtil.getParentActivity(view);
                TextView dateView = (TextView) activity.findViewById(R.id.date);
                DateTime newDate = prevDate.withDate(year, monthOfYear, dayOfMonth);
                dateView.setText(dateFmt.print(newDate));
            }
        }, prevDate.getYear(), prevDate.getMonthOfYear(), prevDate.getDayOfMonth()).show();
    }
}

Where is my understanding going wrong?


Solution

  • It seems that Butterknife injection works only if Butterknife.inject() statement is present in the constructor or any of the Android life-cycle methods.

    Hence, I have modified the DateViewClickListener class to accept the activity that it gets called from and use it as the "source" for Butterknife injection something like:

    public class DateViewClickListener implements View.OnClickListener {
    
        @InjectView(R.id.date)
        TextView dateView;
    
    
        private final DateTime prevDate;
        private DateTimeFormatter dateFmt;
    
        public DateViewClickListener(DateTime prevDate, DateTimeFormatter dateFmt, Activity contextActivity) {
            Butterknife.inject(this, contextActivity);
            this.prevDate = prevDate;
            this.dateFmt = dateFmt;
        }
    
        .....
    }
    

    And then instantiate the listener like:

    public class MainActivity extends ActionBarActivity {
    
        .....
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            ......
    
            date.setOnClickListener(new DateViewClickListener(paymentDate, dateFmt, this));
    
            ......
    
        }
    
        .....
    
    }