I'm hoping the wisdom of the crowd can help me solve a strange problem. I have a very basic ATV Leanback App using androidx.leanback.app.BrowseSupportFragment.

I have subclassed the BrowseSupportFragment and everything works fine. That is until I set up either a setOnHeaderViewSelectedListener or a setOnHeaderClickedListener. Setting either one Breaks the normal "out of the box" navigation.

What I would like to accomplish is setting up either a setOnHeaderViewSelectedListener or a setOnHeaderClickedListener so I can monitor these events without breaking the normal "out of the box" navigation.

There is no super class to call so I can't do it that way. I'm posting the relevant code below and the full source code is readily available online. What am I missing?

Note I have checked many other issues on Stackoverflow but I have not found any that address this issue with an actual working fix. e.g. Adding a listener and still have the normal "out of the box" navigation work exactly the same as in did before the listener was added.

I'm wondering if I'm going to be forced to just copy the entire androidx.leanback.app.BrowseSupportFragment and /or androidx.leanback.app.HeadersSupportFragment source as a way forward but that just does not seem right.

Thank you for your help.

My code

    public class WatchTVFragment extends BrowseSupportFragment {
    
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    
        if (getHeadersSupportFragment() != null){

            getHeadersSupportFragment().setOnHeaderClickedListener(new HeadersSupportFragment.OnHeaderClickedListener() {
                @Override
                public void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
                    Log.d("WatchTVFragment", "=============> onHeaderClicked");
    
                }
            });

            getHeadersSupportFragment().setOnHeaderViewSelectedListener(new HeadersSupportFragment.OnHeaderViewSelectedListener() {
                @Override
                public void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
                    // catch your item by row.getId();
                    Log.d("WatchTVFragment", "=============> onHeaderSelected");
                    //return;
                }
            });
    }

Snippets from androidx/leanback/app/HeadersSupportFragment.java


    public void setOnHeaderViewSelectedListener(OnHeaderViewSelectedListener listener) {
        mOnHeaderViewSelectedListener = listener;
    }


    /**
     * Interface definition for a callback to be invoked when a header item is selected.
     */
    public interface OnHeaderViewSelectedListener {
        /**
         * Called when a header item has been selected.
         *
         * @param viewHolder Row ViewHolder object corresponding to the selected Header.
         * @param row Row object corresponding to the selected Header.
         */
        void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row);
    }

    private OnHeaderViewSelectedListener mOnHeaderViewSelectedListener;

    @Override
    void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder,
            int position, int subposition) {
        if (mOnHeaderViewSelectedListener != null) {
            if (viewHolder != null && position >= 0) {
                ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) viewHolder;
                mOnHeaderViewSelectedListener.onHeaderSelected(
                        (RowHeaderPresenter.ViewHolder) vh.getViewHolder(), (Row) vh.getItem());
            } else {
                mOnHeaderViewSelectedListener.onHeaderSelected(null, null);
            }
        }
    }

Snippets from androidx/leanback/app/BrowseSupportFragment.java

    HeadersSupportFragment mHeadersSupportFragment;

    /**
     * Get currently bound HeadersSupportFragment or null if HeadersSupportFragment has not been created yet.
     * @return Currently bound HeadersSupportFragment or null if HeadersSupportFragment has not been created yet.
     */
    public HeadersSupportFragment getHeadersSupportFragment() {
        return mHeadersSupportFragment;
    }

    private HeadersSupportFragment.OnHeaderViewSelectedListener mHeaderViewSelectedListener =
            new HeadersSupportFragment.OnHeaderViewSelectedListener() {
        @Override
        public void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
            int position = mHeadersSupportFragment.getSelectedPosition();
            if (DEBUG) Log.v(TAG, "header selected position " + position);
            // Layout of Headers Fragment in hidden state may triggers the onRowSelected and
            // reset to 0. Skip in that case.
            if (mShowingHeaders) {
                onRowSelected(position);
            }
        }
    };

1

There are 1 best solutions below

0
On

Just Following up...

I have not found a solution yet but I came across this post Building for Android TV — Episode 4 that referenced this same issue and had a solution. The only problem is this only works for leanback-v17:22.1.1

The solution, again, must be found in Leanback’s inner working mechanisms. HeadersFragment (and its base class BaseRowFragment) is simply a wrapper around a VerticalGridView object which, in turn, is a wrapper around a RecyclerView. As we can see here, BaseRowFragment attaches a OnChildSelectedListener instance to the VerticalGridView, calling its onRowSelected() method whenever a selection occurs. HeadersFragment overrides this method and invokes the onHeaderSelected() method of its (private) OnHeaderViewSelectedListener listener.

Nice. We can then take over the HeadersFragment listener by setting our own directly on the VerticalGridView. The code is pretty straightforward:


VerticalGridView grid = getActivity().getVerticalGridView(this);
grid.setOnChildSelectedListener(new OnChildSelectedListener() {
   @Override
   public void onChildSelected(ViewGroup viewGroup, View view, int i, long l) {
      // we can now update the content accordingly!
   }
});

Will follow up if I can get a work around.