4

My Title is kind of hard to understand but basically when I add items into my database is should display it in a RecyclerView. Now in my RecyclerView I have two layouts but the problem is the first item of my database goes behind my first item in my other layout. So if I have 3 items in my database, it shows only 2 items from the database and the first item hides behind my first item in the RecyclerView which is a different layout that does not use the database at all.

This is my code:

 FirebaseRecyclerOptions<Event> firebaseRecyclerOptions = new FirebaseRecyclerOptions.Builder<Event>()
            .setQuery(query1, Event.class).build();
    AccAdapter = new FirebaseRecyclerAdapter<Event, RecyclerView.ViewHolder>(firebaseRecyclerOptions){

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


        @Override
        public int getItemViewType(int position) {
            if (position == 0) return TYPE_HEADER;
            return TYPE_ITEM;
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

            if (viewType == TYPE_HEADER){
                View view = LayoutInflater.from(getActivity()).inflate(R.layout.recycler_view_row_add_items,
                        parent, false);
                return new ProdudctHolder3(view);
            } else {
                View view = LayoutInflater.from(getActivity()).inflate(R.layout.recycler_view_row_acc,
                        parent, false);
                return new ProductHolder2(view);
            }
        }

        @Override
        protected void onBindViewHolder(final RecyclerView.ViewHolder holder, int position, final Event model) {
            if (holder instanceof ProdudctHolder3){
                ((ProdudctHolder3) holder).addBackground.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        startActivity(new Intent(getActivity(), AccAddItems.class ));
                    }
                });
            } else{

                final ProductHolder2 productHolder2 = (ProductHolder2) holder;


                productHolder2.mName.setText(model.getName());
                productHolder2.view.setBackgroundResource(getBackgroundDrawable(Integer.valueOf(model.getProductAmount())));
                productHolder2.mbackground.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {

                        dialog = new Dialog(getActivity());
                        dialog.setContentView(R.layout.popup_edit_product);

                        SeekBar amountSeekBar = dialog.findViewById(R.id.amountSeekBar);
                        amountSeekBar.setMax(100);
                        amountSeekBar.setProgress(Integer.valueOf(model.getProductAmount()));
                        amountSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                            @Override
                            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                                progress = i;
                            }

                            @Override
                            public void onStartTrackingTouch(SeekBar seekBar) {

                            }

                            @Override
                            public void onStopTrackingTouch(SeekBar seekBar) {
                                getRef(holder.getAdapterPosition()).child("productAmount").setValue(String.valueOf(progress));
                                dialog.dismiss();

                            }
                        });

                        dialog.show();

                    }
                });

                productHolder2.mbackground.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View view) {
                        final PopupMenu popupMenu = new PopupMenu(getActivity(), productHolder2.mbackground);

                        popupMenu.inflate(R.menu.menu_acc);
                        popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                            @Override
                            public boolean onMenuItemClick(MenuItem menuItem) {
                                switch (menuItem.getItemId()){
                                    case R.id.deleteProduct:
                                        getRef(productHolder2.getAdapterPosition()).removeValue();
                                        popupMenu.dismiss();
                                        return true;

                                    default:
                                        return false;
                                }
                            }
                        });

                        popupMenu.show();

                        return true;
                    }
                });

            }
        }

    };

    mAccRecyclerViewRef.setAdapter(AccAdapter);

My two Product Holders

private class ProdudctHolder3 extends RecyclerView.ViewHolder{

    private RelativeLayout addBackground;

    public ProdudctHolder3(View itemView) {
        super(itemView);
        addBackground = itemView.findViewById(R.id.mBackground2);
    }
}

private class ProductHolder2 extends RecyclerView.ViewHolder{

    private TextView mName;
    private RelativeLayout mbackground;
    private View view;

    public ProductHolder2(View itemView) {
        super(itemView);

        mName = itemView.findViewById(R.id.ItemName);
        mbackground = itemView.findViewById(R.id.mBackground1);
        view = itemView.findViewById(R.id.amountIndicator);

    }
}
Kristofer
  • 809
  • 9
  • 24

2 Answers2

3

The ideal solution would have been to set two adapters on a single RecyclerView but unfortunatelly this is not possible. However, you can make a single custom Adapter that handles two types of items. I will explain this by getting an example.

Let's assume you need to display objects of two types, humans and aliens. Your objects require completely different layouts and completely different ViewHolders. Please see the below code for the ViewHolders:

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private static class HumanViewHolder extends RecyclerView.ViewHolder {
        public HumanViewHolder(View itemView) {
            super(itemView);

            //Prepare your ViewHolder
        }

        public void bind(Human human) {

            //Display your human object
        }
    }

    private static class AlienViewHolder extends RecyclerView.ViewHolder {
        public AlienViewHolder(View itemView) {
            super(itemView);

            //Prepare your ViewHolder
        }

        public void bind(Alien alien) {

            //Display your alien object
        }
    }
}

First you need to add two different constants to your adapter representing both type of views:

private static final int ITEM_TYPE_HUMAN;
private static final int ITEM_TYPE_ALIEN;

To keep things simple, let's also assume you store your objects in a list:

private List<Object> items = new ArrayList<>();

public MyAdapter(List<Object> items) {
    this.items.addAll(items);

    //Other stuff if needed
}

Now, the first you need to do, is to implement getItemViewType() method:

@Override
public int getItemViewType(int position) {
    if (items.get(position) instanceof Human) {
        return ITEM_TYPE_HUMAN;
    } else {
        return ITEM_TYPE_ALIEN;
    }
} 

Second, you need to use the item type inside the onCreateViewHolder() method like this:

@Override 
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)  {
    LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());

    if (viewType == ITEM_TYPE_HUMAN) {
        View view = layoutInflater.inflate(R.layout.item_human, parent, false);

        return new HumanViewHolder(view);
    } else {      
        View view = layoutInflater.inflate(R.layout.item_alien, parent, false);

        return new AlienViewHolder(view);
    } 
} 

In the end, you just need to bind the proper view holder like this:

@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
    Object item = items.get(position);

    if (viewHolder instanceof HumanViewHolder) {
        ((HumanViewHolder) viewHolder).bind((Human) item);
    } else {
        ((AlienViewHolder) viewHolder).bind((Alien) item);
    } 
} 
Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • Hey the problem with this is the onCreateViewHolder. Using FirebaseRecyclerAdapter I cannot return a different ViewHolder as suggested above. – Kristofer Jun 15 '18 at 06:01
  • In this case, do it directly in your holder class. The same principle apllies, right? – Alex Mamo Jun 15 '18 at 08:27
  • and thats what I have. Check my code for the ProductHolder class I prepare my Viewholder based on viewType value – Kristofer Jun 15 '18 at 16:50
  • Have you tried to use that if statement but only with a single TextView? – Alex Mamo Jun 15 '18 at 17:06
  • what do you mean? sorry – Kristofer Jun 15 '18 at 17:18
  • I mean to use only one TExtView instead of all those views (mbackground, addBackground), just to see if it works. – Alex Mamo Jun 15 '18 at 17:19
  • It works the problem is that, lets say for example I have 2 items in my database, it only shows 1 item from the database and another one which is the default button that should always be there. – Kristofer Jun 15 '18 at 17:36
  • This doesn't seem to work as I cannot use two ViewHolders in RecyclerViewAdapter – Kristofer Jun 20 '18 at 03:05
  • You can have as many holders as you want but you can use only one at a time. So you can inflate either one or the other layout, according to the type of the object, right? – Alex Mamo Jun 20 '18 at 06:53
  • right. But how would I let the adapter know to move the position to +1 so that part of the data is not hidden – Kristofer Jun 20 '18 at 17:51
  • You don't need to tel the adapter to move to the next position, you only need to use the corresponding holder according to the type of object it is. With other words, while creating each `item view` for the the `RecyclerView` by getting the data from the adapter, each item view should have a different layout according to the type of data the adapter provides. There is nothing more that you need to do. Is it ok now? – Alex Mamo Jun 21 '18 at 06:39
  • I know the problem still persists even though I have done that. – Kristofer Jun 21 '18 at 16:22
  • It seems that there is another problem which I cannot see using only the code you have shared. Maybe you can share somehow your changed code to take a closer look. – Alex Mamo Jun 21 '18 at 16:26
  • I just pasted the whole code instead so taht you can see – Kristofer Jun 21 '18 at 16:28
  • Seeing your code, I can say that you haven't tried to use the idea from my answer. You keept your idea and tried to adapt which I can see that it doesn't work at all. It's to broad so I can answer in a comment but what can I say to you is to try to do the exact things that I showed you in my aswer starting from scratch and do things one step after another. Keep in mind that you cannot using [two differenty queries](https://stackoverflow.com/questions/50592325/is-there-a-way-to-paginate-queries-by-combining-query-cursors-using-firestorerec). – Alex Mamo Jun 21 '18 at 17:36
  • Also don't try to hide view by seetting the visibility to `GONE` because that view will fall on the remaing place. – Alex Mamo Jun 21 '18 at 17:36
  • I've updated it to match yours, also the question, and the same problem still persists. If I delete my item the first layout is also removed. It's like whatever position it is on firebase it takes the first layout. Therefore you cannot see the first item – Kristofer Jun 22 '18 at 19:10
0

Why use the Firebase Recycler Adapter when you could easily make a custom one? If I understood well you want an item to be fixed at position 0 (header) while others will be added below the first one, right? If so, here is a solution I like to use:

public interface ViewType {
    public int getViewType();
}

public interface ViewTypeDelegateAdapter {
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent);
    public void onBindViewHolder(RecyclerView.ViewHolder holder, ViewType item);
}

public class ViewTypes {
    public static int HEADER = 0
    public static int ITEM = 1
}

public class ProductDelegateAdapter implements ViewTypeDelegateAdapter {

    private int resID;
    private Context context;

    public ProductDelegateAdapter(int resID, Context context) {
        this.resID = resID;
        this.context = context;
    }

    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
        return new ProductHolder(LayoutInflater.from(parent.context).inflate(resID, parent, false));
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, ViewType item) {
        (holder as ProductHolder).bind("test");
    }

    class ProductHolder extends RecyclerView.ViewHolder {

        public ProductHolder(View view) {
            super(view);
        }

        public void bind(String test) {

        }

    }

}

public class HeaderDelegateAdapter implements ViewTypeDelegateAdapter {

    private int resID;
    private Context context;

    public ProductDelegateAdapter(int resID, Context context) {
        this.resID = resID;
        this.context = context;
    }

    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
        return new HeaderHolder(LayoutInflater.from(parent.context).inflate(resID, parent, false));
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, ViewType item) {
        (holder as HeaderHolder).bind("test");
    }

    class HeaderHolder extends RecyclerView.ViewHolder {

        public HeaderHolder(View view) {
            super(view);
        }

        public void bind(String test) {

        }

    }

}

public class AccAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private Context context;
    private List<ViewType> items;
    private SparseArrayCompat<ViewTypeDelegateAdapter> delegateAdapters;
    private ViewType headerItem;

    public AccAdapter(Context context) {
        this.context = context;
        this.items = new ArrayList();
        this.delegateAdapters = new SparseArrayCompat();
        this.headerItem = new ViewType() {
            @Override
            public int getViewType() {
                return ViewTypes.HEADER;
            }
        };

        this.items.add(this.headerItem);
        this.delegateAdapters.put(ViewTypes.HEADER, HeaderDelegateAdapter(R.id.test, this.context));
        this.delegateAdapters.put(ViewTypes.ITEM, ProductDelegateAdapter(R.id.test, this.context));
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, Int viewType) {
        return delegateAdapters.get(viewType).onCreateViewHolder(parent);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, Int position) {
        delegateAdapters.get(getItemViewType(position)).onBindViewHolder(holder, items[position])
    }

    @Override
    public Int getItemViewType(Int position) {
        return items.get(position).getViewType();
    }

    @Override
    public Int getItemCount() {
        return items.getSize();
    }

    public void add(ViewType viewType) {
        val initPosition = this.items.size - 1
        this.items.add(item)
        notifyItemRangeChanged(initPosition, this.items.size + 1)
    }

}

public class Event implements ViewType {

    private String id;

    @Override
    public int getViewType() {
        return ViewTypes.ITEM;
    }

}

Excuse me for some syntax errors, I've translated to Java from Kotlin to help you. Hope it helps!

Danilo Lemes
  • 2,342
  • 1
  • 14
  • 16
  • 1
    Im sorry but the reason I used FirebaseRecyclerAdapter was so that it updates data in realtime with other benefits as well. – Kristofer Jun 11 '18 at 22:22
  • You can easily make that happen without using the FirebaseRecyclerAdapter. You can make the structure this way and then register a listener to your Firebase node and then just call the add method from the "main adapter". Much more clean and concise. – Danilo Lemes Jun 12 '18 at 00:01
  • is there a way to possibly solve this problem with firebaserecyclerAdapter – Kristofer Jun 20 '18 at 03:37