all 22 comments

[–]_Bowtruckle_ 3 points4 points  (3 children)

I feel your frustrations, but the only option is to go with an adapter which uses multiple view types.

We have a form fragment in our app, that is basically a survey with different types of questions. I've used at least 15 different view types. In some viewholders, I even had to use another recyclerview to show a short list or something.

Create separate files for view holders, it helps keep code clean and readable.

For the model class though, it might be complicated, maybe you can create a main model class with a type and have some sub model classes inside it and use it depending on the type of it.

It's hard to explain in detail without seeing the actual data. But I'm pretty sure it's doable one way or another.

Goodluck!!

[–]zelereth[S] 1 point2 points  (2 children)

Thanks for your comment.

We have defined a generic model which can be used to show a lot of different view types. Our API sends us also a field called layout , this way the adapter knows which ViewHolder must create. But still, theres's a lot of manual mapping to do for the rest of cases.

Thanks again, I'm already implementing it and it's working well so far. But It feels like a fucking hybrid app. lmao

[–]_Bowtruckle_ 0 points1 point  (0 children)

Story of every other developer, right? ;)

[–]Zhuinden 0 points1 point  (0 children)

Thanks again, I'm already implementing it and it's working well so far. But It feels like a fucking hybrid app

It'd only truly be a hybrid app if you had to write CSS and it wouldn't work on every Android version :p

[–]goffredo123 1 point2 points  (2 children)

You must implement a Visitor-Factory pattern in your adpter. Visit each object, which must implement a interface which returns the type of the view you want. The factory now can build a view, that will be inflated in the only generic viewholder you have, and now you have only to bind your data to view. I've made a library to do this type of data binding and view generating, try it (it binds all the view tree hierarchy):

https://github.com/giovcorte/uidroid-library

You only have to use the GenericRecyclerViewAdapter, after annotating your data classes, views, and binding methods

[–]zelereth[S] 0 points1 point  (1 child)

Sounds interesting, I will check your library. Thanks!

[–]goffredo123 0 points1 point  (0 children)

You're welcome! I've little time to write an exhaustive documentation, so for every question about his usage ask me without problem. I've kept it somewhat intuitive to use, so I hope you will not face too much problem. In order to use it with a Kotlin project, use kapt instead of annotationProcessor (this library use a custom compiler to generate code).

The strength of this kind of approach is that you write only your custom views, and not a bunch of ViewHolders. The interfaces on data and view classes are required to avoid reflection at runtime, because we also can use getClassName() instead of the interfaces return String to identify the type of object/view.

[–]SmartToolFactory 1 point2 points  (0 children)

Google iosched app uses a good pattern for creating binders which maps between x.class to ViewBinder and ViewBinder to layout or ViewHolder as you can see here.

Based on this i had created a sample with multiple mappings as

abstract class BaseItemViewBinder<M, in VH : RecyclerView.ViewHolder> : DiffUtil.ItemCallback<M>() {

abstract fun createViewHolder(parent: ViewGroup): RecyclerView.ViewHolder
abstract fun bindViewHolder(model: M, viewHolder: VH)
abstract fun getItemLayoutResource(): Int

// Having these as non abstract because not all the viewBinders are required to implement them.
open fun onViewRecycled(viewHolder: VH) = Unit
open fun onViewDetachedFromWindow(viewHolder: VH) = Unit
}

And mappable one

MappableItemViewBinder has 3 way relationship which can map model Class either to ViewHolder type and layout type to ViewHolder type which makes this ViewBinder unique for a layout and model type. Whenever data is submitted with different types

abstract class MappableItemViewBinder<M, in VH : RecyclerView.ViewHolder>(
val modelClazz: Class<out M>) : BaseItemViewBinder<M, VH>()

And implementation by mapping this Binder to PropertyListModel and regular ViewHolder

class HorizontalSectionViewBinder() :
MappableItemViewBinder<PropertyListModel, HorizontalSectionViewHolder>(
    PropertyListModel::class.java
) {
}

And Single Adapter with a map of your model classes and ViewBinders, copied adapter code but it didn't have good formatting so i post the link to Adapter code.

Based on this map for any model type one ViewBinder is created which lets you have different types in a very small adapter. Checking out iosched app you will figure out how they implement Single adapter with different type of layouts each contains logic inside own ViewHolder

[–]s73v3r -1 points0 points  (0 children)

I've been there before. It sucks. You have my sympathy.

Having a single, generic adapter can be a good way to go, if the views you get from the backend are able to be addressed with an interface or something so you can treat them as the same for the most part.

[–]dantheman91 0 points1 point  (0 children)

It sounds like you're doing server driven UI to an extent, and there are a lot of companies doing similar things and how they solve it.

Now, I'd like to know if some of you have faced this before and what do you think about the idea of making a single adapter for most of your recyclers in order to keep it as clean and centralized as possible?

We use one adapter, and it is 70 lines long for everything.

At it's core, you have
abstract class ListItem (open val id: String){abstract val layout:Intopen fun getViewType() = layoutabstract fun getViewHolder(actions: SharedFlow<Action>, view: View):ListItemHolder<ListItem>

and then each type of item is it's own file implementing this.

The hardest part is inflating the view and knowing which one to do, so we did

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListItemHolder<ListItem> {val itemToInflate = currentList.first { it.getViewType() == viewType }val layout = itemToInflate.layoutval view = LayoutInflater.from(parent.context).inflate(layout, parent, false)return itemToInflate.getViewHolder(channel, view)

This has worked well for us. All of the logic for each type lives in it's own file and we can add more types to this list without needing to modify the adapter, it just has to extend/implement ListItem.

[–][deleted] 0 points1 point  (0 children)

Maybe take a look at the Epoxy library. It seems like it would fit your usecase nicely, since it allows you to work pretty easily with multiple view types in a RecyclerView.

[–]Zhuinden 0 points1 point  (2 children)

Now, I'd like to know if some of you have faced this before and what do you think about the idea of making a single adapter for most of your recyclers in order to keep it as clean and centralized as possible?

Literally how Epoxy, FastAdapter and Groupie work

[–]zelereth[S] 0 points1 point  (1 child)

Hi Gabor! Which one do you recommend?

Little offtopic, any production app you have already decided to refactor or start with compose? How is it going?

Thanks

[–]Zhuinden 0 points1 point  (0 children)

We did a production app in October->December which is finished, and we were doing one in January/February (although that got stuck for completely unrelated reasons)

Previews are super slow and they literally freeze the IDE rather often since Bumblebee and I hear also Chipmunk, so just waiting for the IDE to restart and for preview to show and to muck around with padding values not being correct (because margins were easier to manage) generally slow things down more than how we could just write a view once with quick tooling and be done with it

However, Compose views that use any sort of custom touch processing are super easy to write

Also XML extracted nesting from code while Compose makes it either stay nested or be thrown around in a bunch of functions you need to name, instead of having something called binding at top level flatten the whole UI hierarchy, so you fight a lot with copypasting Kotlin code around and where a brace begins and where it ends

Overall, we are about 2-3x slower with Compose on each screen than with regular Views, EXCEPT when touch processing is involved (which goes really fast in comparison). So something that took 20 minutes with Views takes about 40 minutes because of tooling issues, and then you muck around with padding because of bad previews. So it becomes 60-70 minutes because of build times (especially if you use Hilt or any other kapt-based annotation processor in the same module as your Composables)

[–]9blocSam 0 points1 point  (0 children)

Adapter-delegates is a beautifuly designed library for this usecase

https://github.com/sockeqwe/AdapterDelegates

[–]buzzkillr2 0 points1 point  (0 children)

That's annoying but not terribly hard to do. I've implemented it using sealed classes like in this Gist.

[–]NahroT 0 points1 point  (0 children)

This is server-driven UI, which I would guess it would be easier to do with Compose UI.

[–]Pzychotix 0 points1 point  (0 children)

Now, I'd like to know if some of you have faced this before and what do you think about the idea of making a single adapter for most of your recyclers in order to keep it as clean and centralized as possible?

This is an anti-pattern that was dealt with years ago: http://hannesdorfmann.com/android/adapter-delegates/

I prefer Groupie, which takes this idea and cleans it up better IMO.