2

I need to add a header view in RecyclerView and the adapter I am using is from Firebase ui.

For a normal RecyclerView adapter I would simply handle different view types and deal with some count/position offsets.

But following the same approach in FirebaseAdapter seems like a bad practice, what would be the best approach in this case.

This is the closest I could get.

       mAdapter = new FirebaseRecyclerAdapter<Comment, RecyclerView.ViewHolder>(Comment.class,
            R.layout.item_comment, CommentHolder.class, ref) {

        final static int TYPE_HEADER = 0;
        final static int TYPE_ITEM = 1;

        @Override
        protected void populateViewHolder(RecyclerView.ViewHolder viewHolder, Comment comment, int position) {
            //cast to comment holder
            final CommentHolder commentViewHolder = (CommentHolder) viewHolder;

            //initialize view
            commentViewHolder.setComment(comment.getComment());
            commentViewHolder.setTime(comment.getTimestamp());

            //get user details and set view
            DatabaseReference ref = FirebaseDatabase.getInstance().getReference(FireContract.PATH_USER_PROFILE)
                    .child(comment.getUserKey());
            ref.addListenerForSingleValueEvent(new ValueEventListener() {
                @Override
                public void onDataChange(DataSnapshot dataSnapshot) {
                    User user = dataSnapshot.getValue(User.class);
                    if(user != null) {
                        commentViewHolder.setName(user.getName());
                        commentViewHolder.setUsername("@" + user.getUsername());
                    }
                }

                @Override
                public void onCancelled(DatabaseError databaseError) {

                }
            });
        }

        //return header view holder is type is header else return super from firebase ui
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if(viewType == TYPE_HEADER){
                View view = LayoutInflater.from(getActivity()).inflate(R.layout.post_details,
                        parent, false);

                return new PostViewHolder(view);
            }
            else
                return super.onCreateViewHolder(parent, viewType);
        }

        //initialize post details as header if position is 0 else call super with position less
        //by one to cater for the header offset
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
            if(position == 0)
                initializePostDetails((PostViewHolder) viewHolder);
            else
                super.onBindViewHolder(viewHolder, position - 1);
        }

        //return view type header if position is 0 else call super from firebase ui
        @Override
        public int getItemViewType(int position) {
            if(position == 0)
                return TYPE_HEADER;

            return super.getItemViewType(position - 1);
        }

        //set count equal to 1 plus actual list items, 1 is added for header view
        @Override
        public int getItemCount() {
            return super.getItemCount() + 1;
        }

    };

but this is giving a cannot resolve constructor issue but it may not work even after resolving this.

adjuremods
  • 2,938
  • 2
  • 12
  • 17
Nouman Tahir
  • 819
  • 1
  • 9
  • 26

2 Answers2

1

So, here is what I was able to upgrade my adapter to, it did work for me but not sure if its still the best approach for achieving this target. It ll be great if someone could verify this or may propose a better simpler solution

mAdapter = new FirebaseRecyclerAdapter<Comment, RecyclerView.ViewHolder>(Comment.class,
            R.layout.item_comment, RecyclerView.ViewHolder.class, ref) {

        final static int TYPE_HEADER = 0;
        final static int TYPE_ITEM = 1;

        @Override
        protected void populateViewHolder(RecyclerView.ViewHolder viewHolder, Comment comment, int position) {
            Log.d(TAG, "running populateViewHolder");
        }

        //return header view holder is type is header else return super from firebase ui
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if(viewType == TYPE_HEADER){
                View view = LayoutInflater.from(getActivity()).inflate(R.layout.post_details,
                        parent, false);

                return new PostViewHolder(view);
            }
            //if type is not header its item view, so return commentHolder instance
            else{
                View view = LayoutInflater.from(getActivity()).inflate(R.layout.item_comment,
                        parent, false);

                return new CommentHolder(view);
            }

        }

        //initialize post details as header if position is 0 else call super with position less
        //by one to cater for the header offset
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
            //if holder is post view holder, initialize header
            if(viewHolder instanceof PostViewHolder)
                initializePostDetails((PostViewHolder) viewHolder);

            else{
                //cast to comment holder
                final CommentHolder commentViewHolder = (CommentHolder) viewHolder;
                Comment comment = getItem(position);

                //initialize view
                commentViewHolder.setComment(comment.getComment());
                commentViewHolder.setTime(comment.getTimestamp());

                //get user details and set view
                DatabaseReference ref = FirebaseDatabase.getInstance().getReference(FireContract.PATH_USER_PROFILE)
                        .child(comment.getUserKey());
                ref.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        User user = dataSnapshot.getValue(User.class);
                        if(user != null) {
                            commentViewHolder.setName(user.getName());
                            commentViewHolder.setUsername("@" + user.getUsername());
                        }
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
            }

        }

        //return view type header if position is 0 else call super from firebase ui
        @Override
        public int getItemViewType(int position) {
            if(position == 0)
                return TYPE_HEADER;

            return TYPE_ITEM;
        }

        //set count equal to 1 plus actual list items, 1 is added for header view
        @Override
        public int getItemCount() {
            return super.getItemCount() + 1;
        }

        //return comment item, decrement 1 from position to avoid out of rand exception as it was
        //incremented for adding header
        @Override
        public Comment getItem(int position) {
            return super.getItem(position - 1);
        }
    };
Nouman Tahir
  • 819
  • 1
  • 9
  • 26
  • so the problem with solution is, every time an item change or insertion notification is fired by firebase it gets directed to the previous item means the change was supposed for 4, it with goto item 5, and there is no way yet found to avoid that – Nouman Tahir Nov 17 '16 at 15:19
0

UPDATE (6 Dec 2016): It's all a bit hacky if you need to check for an empty FirebaseRecyclerView, but so far this is the only way I know to do it. So to sum up, you need to take care of two cases.

  1. When there are item in the list -- You can add an Item Decorator to the Recycler View to make a header when there are items in the list. (as I put in the original post, or as seen here). If you don't need to worry about empty recycler views, this is all you need to do.

  2. When the list is empty -- the Item Decorator can't display, so you can add a view that you switch between View.VISIBLE (for an empty list) and View.GONE (for a filled list). (for more info check out this link)

    View emptyView = findViewById(R.id.empty_view);
    dataBaseReference.addListenerValueEvent(new ValueEventListener() {
         @Override
         public void onDataChange(DataSnapshot dataSnapshot) {
              if (dataSnapshot.hasChildren()) {
                    emptyView.setVisibility(View.GONE);
                 } else {
                    emptyView.setVisibility(View.VISIBLE);
                 }
    
         @Override
         public void onCancelled(DatabaseError databaseError) {
    
         }
    

    });

So once there are things in the list, we take away that empty view, and show the item decorator, which will move with the recycler view scrolling.

**ORIGINAL POST **

I recently ran into the same dilemma you did. I used a solution for a normal RecyclerViewAdapter that works wonderfully, here, which is to just add an ItemDecorator to the recyclerView itself, like this:

recyclerView.addItemDecoration(new HeaderDecoration(this,
                           recyclerView,  R.layout.test_header));

From here you make a custom Item Decorator. If you need it horizontal or anything, you'll mess with the values in there (mostly things that reference the top need to reference the left instead):

public class HeaderDecoration extends RecyclerView.ItemDecoration {

private View mLayout;

public HeaderDecoration(final Context context, RecyclerView parent, @LayoutRes int resId) {
    // inflate and measure the layout
    mLayout = LayoutInflater.from(context).inflate(resId, parent, false);
    mLayout.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
}


@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDraw(c, parent, state);
    // layout basically just gets drawn on the reserved space on top of the first view
    mLayout.layout(parent.getLeft(), 0, parent.getRight(), mLayout.getMeasuredHeight());
    for (int i = 0; i < parent.getChildCount(); i++) {
        View view = parent.getChildAt(i);
        if (parent.getChildAdapterPosition(view) == 0) {
            c.save();
            final int height = mLayout.getMeasuredHeight();
            final int top = view.getTop() - height;
            c.translate(0, top);
            mLayout.draw(c);
            c.restore();
            break;
        }
    }
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    if (parent.getChildAdapterPosition(view) == 0) {
        outRect.set(0, mLayout.getMeasuredHeight(), 0, 0);
    } else {
        outRect.setEmpty();
    }
}

}

The problem with it so far is that with an empty list, there is no item decorator. I'm looking into Empty Views for recycler views, I'll update this when I get that working with Firebase.

Community
  • 1
  • 1
Justin Liu
  • 138
  • 1
  • 9