idclark
idclark

Reputation: 958

Introducing Butterknife library causes runtime exception in fragment class when binding views

I started to refactor my application to use the Butterknife library for view binding. Everything works as expected in my activity class, but i'm having trouble understanding how to use butterknife appropriately in my static fragment class. My fragment contains a RecyclerView adapter containing a list of CardView elements.

Before Refactor

public static class PlaceholderFragment extends Fragment {

        Activity mActivity;
        RecyclerView mRecyclerView;
        TaskAdapter taskAdapter;
        FloatingActionButton mFabView;
        TextView m_ID;
        TextView mTitle;

...

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

            View rootView = inflater.inflate(R.layout.fragment_main, container, false);
            mRecyclerView = (RecyclerView) rootView.findViewById(R.id.cardList);
            mFabView = (FloatingActionButton) rootView.findViewById(R.id.normal_plus);

            mFabView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

and also

        @Override
        public void onViewCreated(View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            mRecyclerView.setAdapter(taskAdapter);
            mRecyclerView.setHasFixedSize(true);
            mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
            mRecyclerView.setItemAnimator(new DefaultItemAnimator());
            taskAdapter.SetOnItemClickListener(new TaskAdapter.OnItemClickListener() {

                @Override
                public void onItemClick(View v, int position) {
                    m_ID = (TextView) v.findViewById(R.id._task_id);
                    mTitle = (TextView) v.findViewById(R.id.title);
                    Intent detailIntent = new Intent(v.getContext(), DetailActivity.class);
                    detailIntent.putExtra("TASK_ID", m_ID.getText().toString());
                    detailIntent.putExtra("TASK_TITLE", mTitle.getText().toString());

After Refactor

public static class PlaceholderFragment extends Fragment {

        Activity mActivity;
        TaskAdapter taskAdapter;
        @Bind(R.id.cardList) RecyclerView mRecyclerView;
        @Bind(R.id.normal_plus) FloatingActionButton mFabView;
        @Bind(R.id._task_id) TextView m_ID;
        @Bind(R.id.title) TextView mTitle;

...

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

            View rootView = inflater.inflate(R.layout.fragment_main, container, false);
            ButterKnife.bind(this, rootView);

...

        @Override
        public void onViewCreated(View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            mRecyclerView.setAdapter(taskAdapter);
            mRecyclerView.setHasFixedSize(true);
            mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
            mRecyclerView.setItemAnimator(new DefaultItemAnimator());
            taskAdapter.SetOnItemClickListener(new TaskAdapter.OnItemClickListener() {

                @Override
                @OnClick({R.id._task_id, R.id.title})
                public void onItemClick(View v, int position) {
                    ButterKnife.bind(this, v);
                    Intent detailIntent = new Intent(v.getContext(), DetailActivity.class);
                    detailIntent.putExtra("TASK_ID", m_ID.getText().toString());
                    detailIntent.putExtra("TASK_TITLE", mTitle.getText().toString());

The exception i'm seeing is

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.github.idclark.forgetmenot/com.github.idclark.forgetmenot.MainActivity}: java.lang.RuntimeException: Unable to bind views for com.github.idclark.forgetmenot.MainActivity$PlaceholderFragment

...

Caused by: java.lang.RuntimeException: Unable to bind views for com.github.idclark.forgetmenot.MainActivity$PlaceholderFragment
                                                                                    at butterknife.ButterKnife.bind(ButterKnife.java:322)
                                                                                    at butterknife.ButterKnife.bind(ButterKnife.java:279)
                                                                                    at com.github.idclark.forgetmenot.MainActivity$PlaceholderFragment.onCreateView(MainActivity.java:86)

from the stacktrace it looks like that because i'm now effectively trying to bind both m_ID and mTitle in onCreateView() instead of the onItemClick() callback and that's why the views are null. Is that correct? I'm also calling Butterknife.bind() twice, once in onCreteView for the mRecyclerView and mFabView, but Butterknife.bind() is also called in the callback. How can bind the m_ID and mTitle views at the appropriate time / what method beside onCreateView should I use?

Upvotes: 1

Views: 444

Answers (1)

Konstantin Loginov
Konstantin Loginov

Reputation: 16010

You should ButterKnife.bind(...) only once, in onCreateView(), just as you did.

For callback, you should cast you View to actual custom View class (which is also binding it's widgets!), and get title and id from there:

public class MyCustomView extends FrameLayout {

    public @Bind(R.id._task_id) TextView m_ID;
    public @Bind(R.id.title)  TextView mTitle;

    public MyCustomView(Context context) {
        super(context);
        initialize(context);
    }

    public MyCustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize(context);
    }

    void initialize(Context context) {
        LayoutInflater.from(context).inflate(R.layout.my_custom_view, this);
        ButterKnife.bind(this);
    }
}

And then you have to

  • Remove TextView's references from PlaceholderFragment

      TextView m_ID;
      TextView mTitle;
    
  • Fix handler:

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mRecyclerView.setAdapter(taskAdapter);
        mRecyclerView.setHasFixedSize(true);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        taskAdapter.SetOnItemClickListener(new TaskAdapter.OnItemClickListener() {
    
            @Override
            @OnClick({R.id._task_id, R.id.title})
            public void onItemClick(View v, int position) {
                MyCustomView myView = (MyCustomView)v;
                Intent detailIntent = new Intent(v.getContext(), DetailActivity.class);
                detailIntent.putExtra("TASK_ID", myView .m_ID.getText().toString());
                detailIntent.putExtra("TASK_TITLE", myView .mTitle.getText().toString());
    

I hope, it helps

Upvotes: 1

Related Questions