-2

I have a generic RecycleView for which I have different row classes, in this row class I would like to implement an interface, my objective is to pass the text from OnQueryTextListener to an activity or fragment where the interface is implemented.

public class PlantDocHeader extends LinearLayout implements RecycleViewGenericAdapter.RecyclerViewRowHeader<PlantDocViewModel> {

    private Button buttonQuestion;
    private Button buttonPosts;
    private TextView searchTxtField;
    private ImageView imageViewExpandSearch;
    private SearchView searchView;
    private boolean expand = true;

    SearchView.OnQueryTextListener onQueryTextListener;
    private onTextChange onTextChange;

    Resources res;

    public PlantDocHeader(Context context) {
        super(context);
    }

    public PlantDocHeader(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public PlantDocHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        searchTxtField = findViewById(R.id.textView_plant_doc_header_search);
        imageViewExpandSearch = findViewById(R.id.imageView_plant_doc_header_expand_button);
        searchView = findViewById(R.id.searchView_plant_doc_header);
        buttonQuestion = findViewById(R.id.button_plant_doc_header_filter_my_posts);
        buttonPosts = findViewById(R.id.button_plant_doc_header_filter_question);
    }

    @Override
    public void showData(PlantDocViewModel item) {
        res = getResources();

        buttonQuestion.setText(res.getString(R.string.question));
        buttonQuestion.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                AskQuestionFragment askQuestionFragment = new AskQuestionFragment();
                ChangeFragment(askQuestionFragment, (Activity) getContext(), false);
            }
        });


        buttonPosts.setText(res.getString(R.string.my_posts));
        buttonPosts.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                PlantDocMyPostsFragment myPostsFragment = new PlantDocMyPostsFragment();
                ChangeFragment(myPostsFragment, (Activity) getContext(), false);
            }
        });

        searchTxtField.setText(res.getString(R.string.search));
        imageViewExpandSearch.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if(expand){
                    searchView.setVisibility(VISIBLE);
                    imageViewExpandSearch.setImageResource(R.drawable.collapse);
                    expand = false;
                }else{
                    searchView.setVisibility(GONE);
                    imageViewExpandSearch.setImageResource(R.drawable.expand);
                    expand = true;
                }
            }
        });

        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                Toast.makeText(getContext(), ""+newText, Toast.LENGTH_SHORT).show();
                onTextChange.textSearch(newText);
                return true;
            }
        });
    }

    public interface onTextChange {
        void textSearch(String searchText);
    }

}

This is the error I am getting

Attempt to invoke interface method 'void com.gardify.android.UI.PlantDoc.PlantDocHeader$onTextChange.textSearch(java.lang.String)' on a null object reference

RecycleViewGenericAdapter

/**
 * @param <T> is generic parameter type provided to List, OnRecyclerViewItemClickListener and RecyclerViewRow
 * @param <V> generic type for header view Model
 * @param <E> generic type for footer view Model
 */
public class RecycleViewGenericAdapter<T, V, E> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private List<T> mDataset;
    private V mHeaderData;
    private E mFooterData;

    private static final int TYPE_HEADER = 000;
    private static final int TYPE_ITEM = 111;
    private static final int TYPE_FOOTER = 222;

    private OnItemClickListener<T> onItemClickListener;
    private OnItemClickListenerHeader<V> onItemClickListenerHeader;
    private OnItemClickListenerFooter<E> onItemClickListenerFooter;

    private int layoutId, layoutIdHeader, layoutIdFooter;

    public RecycleViewGenericAdapter(List<T> mDataset, int layoutId, V mHeaderData, int layoutIdHeader,
                                     E mFooterData, int layoutIdFooter, OnItemClickListener<T> onItemClickListener,
                                     OnItemClickListenerHeader onItemClickListenerHeader, OnItemClickListenerFooter onItemClickListenerFooter) {
        this.onItemClickListener = onItemClickListener;
        this.onItemClickListenerHeader = onItemClickListenerHeader;
        this.onItemClickListenerFooter = onItemClickListenerFooter;
        this.mDataset = mDataset;
        this.mHeaderData = mHeaderData;
        this.mFooterData = mFooterData;
        this.layoutId = layoutId;
        this.layoutIdHeader = layoutIdHeader;
        this.layoutIdFooter = layoutIdFooter;

    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        RecyclerView.ViewHolder vh = null;
        if (viewType == TYPE_ITEM) {
            RecyclerViewRow<T> row = (RecyclerViewRow<T>) LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
            vh = new ItemViewHolder(row);
        }else if (viewType == TYPE_HEADER) {
            RecyclerViewRowHeader<V> rowHeader = (RecyclerViewRowHeader<V>) LayoutInflater.from(parent.getContext()).inflate(layoutIdHeader, parent, false);
            vh = new HeaderViewHolder(rowHeader);
        } else if(viewType == TYPE_FOOTER) {
            RecyclerViewRowFooter<E> footerRow = (RecyclerViewRowFooter<E>) LayoutInflater.from(parent.getContext()).inflate(layoutIdFooter, parent, false);
            vh = new FooterViewHolder(footerRow);
        }
        return vh;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
        if (holder instanceof RecycleViewGenericAdapter.ItemViewHolder) {
            ItemViewHolder itemViewHolder = (ItemViewHolder) holder;
            itemViewHolder.onBind((mHeaderData!=null? position-1 : position), itemViewHolder);
        } else if (holder instanceof RecycleViewGenericAdapter.HeaderViewHolder) {
            HeaderViewHolder headerViewHolder = (HeaderViewHolder) holder;
            headerViewHolder.onBind(headerViewHolder);
        } else if (holder instanceof RecycleViewGenericAdapter.FooterViewHolder) {
            FooterViewHolder footerViewHolder = (FooterViewHolder) holder;
            footerViewHolder.onBind(footerViewHolder);
        }
    }

    @Override
    public int getItemCount() {
        if (mHeaderData != null && mFooterData != null) {
            return mDataset.size() + 2;
        } else if (mHeaderData != null || mFooterData != null) {
            return mDataset.size() + 1;
        }
        return mDataset.size();
    }

    private int getLastPosition() {
        return getItemCount() - 1;
    }

    private boolean isLastPosition(int position) {
        return position == getLastPosition();
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0 && mHeaderData!=null) {
            return TYPE_HEADER;
        } else if (isLastPosition(position) && mFooterData!=null) {
            return TYPE_FOOTER;
        } else {
            return TYPE_ITEM;
        }
    }

    /**
     * Item ViewHolder
     **/
    public class ItemViewHolder extends RecyclerView.ViewHolder {
        public RecyclerViewRow<T> mRow;

        public ItemViewHolder(RecyclerViewRow<T> itemView) {
            super((View) itemView);
            mRow = itemView;
        }

        private void onBind(final int position, ItemViewHolder viewHolder) {

            viewHolder.mRow.showData(mDataset.get(position));
            ((View) viewHolder.mRow).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (onItemClickListener != null) {
                        onItemClickListener.onItemClick(mDataset.get(position));
                    }
                }
            });
        }
    }

    public interface RecyclerViewRow<T> {
        void showData(T item);
    }

    public interface OnItemClickListener<T> {
        void onItemClick(T position);
    }

    public void updateList(List<T> _mDataset){
        this.mDataset= _mDataset;
        notifyDataSetChanged();
    }

    /**
     * Header ViewHolder
     **/
    public class HeaderViewHolder extends RecyclerView.ViewHolder {
        public RecyclerViewRowHeader<V> mRowHeader;

        public HeaderViewHolder(RecyclerViewRowHeader<V> itemView) {
            super((View) itemView);
            mRowHeader = itemView;
        }

        private void onBind(HeaderViewHolder viewHolder) {
            viewHolder.mRowHeader.showData(mHeaderData);
            ((View) viewHolder.mRowHeader).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (onItemClickListenerHeader != null) {
                        onItemClickListenerHeader.onItemClickHeader(mHeaderData);
                    }
                }
            });
        }
    }

    public interface RecyclerViewRowHeader<V> {
        void showData(V item);
    }

    public interface OnItemClickListenerHeader<V> {
        void onItemClickHeader(V position);
    }

    /**
     * Footer ViewHolder
     **/
    public class FooterViewHolder extends RecyclerView.ViewHolder {
        public RecyclerViewRowFooter<E> mRowFooter;

        public FooterViewHolder(RecyclerViewRowFooter<E> itemView) {
            super((View) itemView);
            mRowFooter = itemView;
        }

        private void onBind(FooterViewHolder viewHolder) {
            viewHolder.mRowFooter.showData(mFooterData);
            ((View) viewHolder.mRowFooter).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (onItemClickListenerFooter != null) {
                        onItemClickListenerFooter.onItemClickFooter(mFooterData);
                    }
                }
            });
        }
    }

    public interface RecyclerViewRowFooter<E> {
        void showData(E item);
    }

    public interface OnItemClickListenerFooter<E> {
        void onItemClickFooter(E position);
    }
}

Usage in Fragment

public class PlantDocFragment extends Fragment implements RecycleViewGenericAdapter.OnItemClickListener<PlantDocViewModel>, PlantDocHeader.OnTextChangeListener {

//...

    RecycleViewGenericAdapter<PlantDocViewModel, PlantDocViewModel, Nullable> adapter = new RecycleViewGenericAdapter<>(plantDocList, R.layout.recycler_view_plant_doc_row_item, plantDocHeader, R.layout.recycler_view_plant_doc_header,
                    null, 0, this, null, null);

//...

@Override
public void textSearch(String searchText) {
    Toast.makeText(getContext(), "searched text : "+searchText, Toast.LENGTH_SHORT).show();
}

the generic recycleview that i am using can be found here

Amir Dora.
  • 2,358
  • 4
  • 28
  • 47
  • 1
    I'm not sure I understand; no class has no constructor. It may be *hidden* (e.g., a singleton) but you can't call an instance method unless you have an instance. Where is this instance supposed to come from? – Dave Newton Dec 21 '20 at 17:25
  • The instance is coming from RecycleView Adapter. I have updated the code. @DaveNewton – Amir Dora. Dec 21 '20 at 17:56
  • 1
    Evidence suggests otherwise. – Dave Newton Dec 21 '20 at 18:00
  • How do you call the onTextChange in the fragment? Can you add more relevant codes from this fragment? Your ```implements``` and the way you initialize onTextChange will be helpful – Prince Ali Dec 21 '20 at 18:42
  • @PrinceAli I implement it on fragment PlantDocHeader.OnTextChangeListener. I have updated the code. My question is regarding initialization, not quite sure where to initialize as I am using LinearLayout and the layout is being used in recycleview. – Amir Dora. Dec 21 '20 at 19:06
  • I'm not quite sure if it'll help but I use an onClickListener interface in my adapter and I have to call a method I created in the adapter from my activity. I'll post a possible answer and will delete it if it doesnt help – Prince Ali Dec 21 '20 at 19:10
  • I don't see a Setter for `onTextChange` in `PlantDocHeader` thats why its always null . There should be a setter for listener . have you forgot to add it with question or its not there ? – ADM Dec 25 '20 at 12:41
  • I wasn't actually sure how I can add it. @ADM – Amir Dora. Dec 25 '20 at 12:42

3 Answers3

1

Basically you need to pass fragments reference to the HeaderView .
For this first of all you have to have a setter in PlantDocHeader as below . I have removed the interface also it seems useless.

public class PlantDocHeader<T> extends LinearLayout {
    private OnTextChangeListener onTextChangeListener;
    public void setOnTextChangeListener(OnTextChangeListener onTextChangeListener) {
        this.onTextChangeListener = onTextChangeListener;
    }
    public void setData(T data) {

    }
}

Rephrase your Adapters constructor a bit its taking a lots of argument a Builder will probably help or you can create separate setters for optional properties.

public class RecycleViewGenericAdapter<T, V, E> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private OnItemClickListener<T> onItemClickListener;
    private OnItemClickListenerHeader<V> onItemClickListenerHeader;
    private OnItemClickListenerFooter<E> onItemClickListenerFooter;
    private PlantDocHeader.OnTextChangeListener onTextChangeListener;
    private int layoutId, layoutIdHeader, layoutIdFooter;

    public RecycleViewGenericAdapter(List<T> mDataset, int layoutId, V mHeaderData, int layoutIdHeader,
                                     E mFooterData, int layoutIdFooter) {
        this.mDataset = mDataset;
        this.mHeaderData = mHeaderData;
        this.mFooterData = mFooterData;
        this.layoutId = layoutId;
        this.layoutIdHeader = layoutIdHeader;
        this.layoutIdFooter = layoutIdFooter;
    }
}

Create separate setters for all listeners and set them as they needed from the calling component. Now when you done setting you need to pass onTextChangeListener to header view.

public class HeaderViewHolder extends RecyclerView.ViewHolder {
    public RecyclerViewRowHeader<V> mRowHeader;
    public HeaderViewHolder(RecyclerViewRowHeader<V> itemView) {
        super((View) itemView);
        mRowHeader = itemView;
    }
    private void onBind(HeaderViewHolder viewHolder) {
        viewHolder.mRowHeader.setOnTextChangeListener(onTextChangeListener);
        viewHolder.mRowHeader.showData(mHeaderData);
        ((View) viewHolder.mRowHeader).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (onItemClickListenerHeader != null) {
                    onItemClickListenerHeader.onItemClickHeader(mHeaderData);
                }
            }
        });
    }
}

Now it should work . also you might wanna consider passing position between listener because there can be multiple headers also if not then it will be 0 always.

PS- If you are trying to create a generic adapter you should consider creating a abstract Adapter class right now your class does not seems generic because you are passing way too many arguments in constructor which should in child class only its not flexible at all. You might wanna check some open source generic adapter for this to see .

ADM
  • 16,256
  • 11
  • 37
  • 69
  • 1
    Thank you for your answer, based on your answer I got the idea, though I had to make small modifications. I will update the changes to the answer. :) Thanks again for your time much appreciated. – Amir Dora. Dec 28 '20 at 12:39
0

You have not told what to do when I call textSearch() method.

You have to implement onTextChange interface (Please follow Java naming convention when declaring interface/class/variables) in some class where you will override textSearch() method and then pass the reference (implemented class) to the variable onTextChange (you have given same name of interface and instance variable so to avoid confusion you can give another name to variable). Then when you call textSearch() in this line onTextChange.textSearch(newText); you will have implementation. And your code will work.

Akash
  • 74
  • 3
0

In your adapter create a public method:

public void setOnTextChange(onTextChange onTextChange) {
        this.onTextChange = onTextChange;
    }

In your fragment, change your implements to implement your interface:

implements PlantDocHeader.OnTextChange

Then set the onTextChange:

adapter.setOnTextChange(this);
Prince Ali
  • 620
  • 6
  • 14
  • 1
    The searchview is coming from custom LinearLayout class and I can not access onTextChange from Recycleview adapter class itself. This doesn't seem to work in my case. – Amir Dora. Dec 24 '20 at 00:04