question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Handling different View Types in the FirebaseRecyclerAdapter

See original GitHub issue

I’m working on a messaging app, and I think that overriding the original getItemViewType(int) in order to handle different view types (eg. sent vs received text layouts) can complicate code with no clear benefit. You can also have a single layout file and hide/show different elements in the populateViewHolder(VH, int, int) method, but I find it an even worse choice.

I’d like to change this, introducing a new constructor: The adapter will handle the possibility of having more than just one view model.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:11
  • Comments:10 (3 by maintainers)

github_iconTop GitHub Comments

6reactions
giansegatocommented, Sep 14, 2016

I see your point. Maybe a dedicated subclass might be a very good solution, in order to avoid unnecessary boilerplate.

My original idea was a constructor like this

public FirebaseRecyclerAdapter(Class<T> modelClass, List<Integer> modelsLayout, List<Class<VH>> viewHolderClasses, Query ref)

in which I would set mModelLayout = -1; and mViewHolderClass = null;

In this way, I can change onCreateViewHolder and getItemViewType in order to handle both the functionalities. Something like this:

@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
    ViewGroup view = (ViewGroup) LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
    try {
        Constructor<VH> constructor = mViewHolderClass != null ?
                mViewHolderClass.getConstructor(View.class) :
                mViewHolderClasses.get(mModelsLayout.indexOf(viewType)).getConstructor(View.class);
        return constructor.newInstance(view);
    } catch (IndexOutOfBoundsException e) {
        throw new RuntimeException(e);
    } /* [...] */
}
@Override
public int getItemViewType(int position) {
    if (mModelLayout == -1)
        throw new RuntimeException("You must override getItemViewType if you provide more than one view types.");
    return mModelLayout;
}

I assume that the items in the list of layouts have the same index of the items in the list of ViewHolders. And I also assume that the developers might want to add their own logic when deciding which view type to apply, depending on the data.

However, a subclass might be a cleaner solution, using the same pattern applied by onBindViewHolder:

@Override
public int getItemViewType(int position) {
    return decideViewType(position);
}

abstract protected void decideViewType(int position);

A third approach could be seeing the multiple-views case as an extension of the particular case of a single view. In this sense, the single-view constructor could create a list with just a single item in it.

Edit: On a second thought, I think that instead of two lists, it would be better to just provide a map: Map<Integer, VH>.

4reactions
SUPERCILEXcommented, Apr 26, 2017

@samtstern I believe this issue can be closed since it falls under the category of “the developer can do this themselves fairly easily and implementing it would make things too complicated for users who just want a quick solution”.

@gwidaz @brad007 @Sottti I know this issue has been open for a while, but if any of you are still in need of a fix, here’s how you would do it:

public class ScoutAdapter extends FirebaseRecyclerAdapter<ScoutMetric, ScoutViewHolderBase> {
    public ScoutAdapter(Query query) {
        super(ScoutUtils.METRIC_PARSER, 0 /* modelLayoutLayoutId: can be anything, it doesn't matter since we are overriding it */, ScoutViewHolderBase.class, query);
        // ...
    }

    @Override
    public void populateViewHolder(ScoutViewHolderBase viewHolder,
                                   ScoutMetric metric,
                                   int position) {
        //noinspection unchecked
        viewHolder.bind(metric, /* ... */);
    }

    /** 
      * This will require a refactor on your part, but the basic idea is to inflate your layout and
      * pass that into your ViewHolder for each different view.
      */
    @Override
    public ScoutViewHolderBase onCreateViewHolder(ViewGroup parent, @MetricType int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        switch (viewType) {
            case MetricType.BOOLEAN:
                return new CheckboxViewHolder(
                        inflater.inflate(R.layout.scout_checkbox, parent, false));
            case MetricType.NUMBER:
                return new CounterViewHolder(
                        inflater.inflate(R.layout.scout_counter, parent, false));
            case MetricType.TEXT:
                return new EditTextViewHolder(
                        inflater.inflate(R.layout.scout_notes, parent, false));
            case MetricType.LIST:
                return new SpinnerViewHolder(
                        inflater.inflate(R.layout.scout_spinner, parent, false));
            case MetricType.STOPWATCH:
                return new StopwatchViewHolder(
                        inflater.inflate(R.layout.scout_stopwatch, parent, false));
            case MetricType.HEADER:
                return new ScoutViewHolderBase<Void, TextView>(
                        inflater.inflate(R.layout.scout_header, parent, false)) {};
            default:
                throw new IllegalStateException();
        }
    }

    @Override
    @MetricType
    public int getItemViewType(int position) {
        // Here you are simply returning an integer to use in onCreateViewHolder.
        // In my case, I'm getting the view type from the database, but you could do it anyway you like.
        return getItem(position).getType(); // This returns one of @MetricType e.g. MetricType.BOOLEAN, etc.
    }
}

If anyone wants to look at my source in a little bit more detail to get a better understanding of how to handle different view types, here’s the full ScoutAdapter.java and the package where I keep my ViewHolders.

Read more comments on GitHub >

github_iconTop Results From Across the Web

FirebaseRecyclerAdapter and multiply item types on android
Your ViewHolder class will need to be able to handle both item types. Then in populateViewHolder you can populate the appropriate views ......
Read more >
FirebaseUI for Realtime Database - Firebase Open Source
FirebaseUI offers two types of RecyclerView adapters for the Realtime Database: FirebaseRecyclerAdapter — binds a Query to a RecyclerView and responds to ...
Read more >
FirebaseRecyclerAdapter using Recyclerview with Firebase ...
In this tutorial, we will learn how to use FirebaseRecyclerAdapter using Recyclerview with Firebase Realtime Database in 2020Link Assets: ...
Read more >
Firebase Recycler Adapter onclick item listener - YouTube
Firebase Recycler Adapter onclick item listener - Handle item ... key android). so that we can display profile information of each user on ......
Read more >
How to populate RecyclerView with Firebase data using ...
How to add RecyclerView in the android app and display the data in Firebase Realtime Database. Step 1: Add the following dependencies to...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found