GithubHelp home page GithubHelp logo

sockeqwe / adapterdelegates Goto Github PK

View Code? Open in Web Editor NEW
2.9K 75.0 314.0 1001 KB

"Favor composition over inheritance" for RecyclerView Adapters

Home Page: http://hannesdorfmann.com/android/adapter-delegates

License: Apache License 2.0

Java 68.13% Kotlin 31.87%
recyclerview recyclerview-adapter delegate composition-over-inheritance adapterdelegates

adapterdelegates's Introduction

AdapterDelegates

Read the motivation for this project in my blog post.

The idea of this library is to build your adapters by composing reusable components.

Favor composition over inheritance

The idea is that you define an AdapterDelegate for each view type. This delegate is responsible for creating ViewHolder and binding ViewHolder for a certain viewtype. Then you can compose your RecyclerView Adapter by registering the AdapterDelegates that you really need.

Changelog

See releases section

Quickstart: Kotlin DSL

There are 2 artifacts for kotlin users that allow you to write Adapter Delegates more convenient by providing a DSL:

Dependencies

implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'

// If you use Kotlin Android Extensions and synthetic properties (alternative to findViewById())
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-layoutcontainer:4.3.2'

// If you use ViewBinding
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'

How to use it

fun catAdapterDelegate(itemClickedListener : (Cat) -> Unit) = adapterDelegate<Cat, Animal>(R.layout.item_cat) {

    // This is the initializer block where you initialize the ViewHolder.
    // Its called one time only in onCreateViewHolder.
    // this is where you can call findViewById() and setup click listeners etc.

    val name : TextView = findViewById(R.id.name)
    name.setClickListener { itemClickedListener(item) } // Item is automatically set for you. It's set lazily though (set in onBindViewHolder()). So only use it for deferred calls like clickListeners.

    bind { diffPayloads -> // diffPayloads is a List<Any> containing the Payload from your DiffUtils
        // This is called anytime onBindViewHolder() is called
        name.text = item.name // Item is of type Cat and is the current bound item.
    }
}

In case you want to use kotlin android extensions and synthetic properties (as alternative to findViewById()) use adapterDelegateLayoutContainer instead of adapterDelegate like this:

fun catAdapterDelegate(itemClickedListener : (Cat) -> Unit) = adapterDelegateLayoutContainer<Cat, Animal>(R.layout.item_cat) {

    name.setClickListener { itemClickedListener(item) } // no need for findViewById(). Name is imported as synthetic property from kotlinx.android.synthetic.main.item_cat

    bind { diffPayloads ->
        name.text = item.name
    }
}

If you use adapterDelegateLayoutContainer() don't forget to add

androidExtensions {
    experimental = true
}

to your build.gradle to enable LayoutContainer.

In case you want to use ViewBinding\DataBinding use adapterDelegateViewBinding instead of adapterDelegate like this:

fun cat2AdapterDelegate(itemClickedListener : (Cat) -> Unit) = adapterDelegateViewBinding<Cat, DisplayableItem, ItemCatBinding>(
    { layoutInflater, root -> ItemCatBinding.inflate(layoutInflater, root, false) }
) {
    binding.name.setOnClickListener {
        itemClickedListener(item)
    }
    bind {
        binding.name.text = item.name
    }
}

You have to specify if a specific AdapterDelegate is responsible for a specific item. Per default this is done with an instanceof check like item instanceof Cat. You can override this if you want to handle it in a custom way by setting the on lambda and return true or false:

adapterDelegate<Cat, Animal> (
    layout = R.layout.item_cat,
    on = { item: Animal, items: List, position: Int ->
        if (item is Cat && position == 0)
            true // return true: this adapterDelegate handles it
        else
            false // return false
    }
){
    ...
    bind { ... }
}

The same on parameter is available for adapterDelegateLayoutContainer() and adapterDelegateViewBinding() DSL.

Compose your Adapter

Finally, you can compose your RecyclerView Adapter by registering your AdapterDelegates like this:

val adapter = ListDelegationAdapter<List<Animal>>(
    catAdapterDelegate(...),
    dogAdapterDelegate(),
    snakeAdapterDelegate()
)

fun vs. val

You could define your AdapterDelegate also as TopLevel val like this:

// top level property inside CatDelegate.kt
val catDelegate = adapterDelegate<Cat, Animal> {
    ...
    bind { ... }
}

but a top level val is a static field at the end so that this adapter delegate will be kept for the lifetime of your application in memory. Therefore, we would recommend to prefer write your AdapterDelegate as a function and call this function to actually instantiate the AdapterDelegate. Then the AdapterDelegate can be garbage collected as soon as the user leaves the screen the AdapterDelegate is used in.

// top level function inside CatDelegate.kt
fun catAdapterDelegate() = adapterDelegate<Cat, Animal> {
   ...
   bind { ... }
}

Dependencies if you don't use Kotlin DSL

This library is available on maven central:

implementation 'com.hannesdorfmann:adapterdelegates4:4.3.2'

Build Status

Please note that since 4.0 the group id has been changed to adapterdelegates4.

Snapshot

implementation 'com.hannesdorfmann:adapterdelegates4:4.3.3-SNAPSHOT'

You also have to add the url to the snapshot repository:

allprojects {
  repositories {
    ...

    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}

How to use it in Java

The idea of this library is to build your adapters by composing reusable components.

Favor composition over inheritance

The idea is that you define an AdapterDelegate for each view type. This delegate is responsible for creating ViewHolder and binding ViewHolder for a certain viewtype. An AdapterDelegate get added to an AdapterDelegatesManager. This manager is the man in the middle between RecyclerView.Adapter and each AdapterDelegate.

For example:

public class CatAdapterDelegate extends AdapterDelegate<List<Animal>> {

  private LayoutInflater inflater;

  public CatAdapterDelegate(Activity activity) {
    inflater = activity.getLayoutInflater();
  }

  @Override public boolean isForViewType(@NonNull List<Animal> items, int position) {
    return items.get(position) instanceof Cat;
  }

  @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
    return new CatViewHolder(inflater.inflate(R.layout.item_cat, parent, false));
  }

  @Override public void onBindViewHolder(@NonNull List<Animal> items, int position,
      @NonNull RecyclerView.ViewHolder holder, @Nullable List<Object> payloads) {

    CatViewHolder vh = (CatViewHolder) holder;
    Cat cat = (Cat) items.get(position);

    vh.name.setText(cat.getName());
  }

  static class CatViewHolder extends RecyclerView.ViewHolder {

    public TextView name;

    public CatViewHolder(View itemView) {
      super(itemView);
      name = (TextView) itemView.findViewById(R.id.name);
    }
  }
}

Please note that onBindViewHolder() last parameter payloads is null unless you use from adapter.notify. There are more methods like onViewRecycled(ViewHolder), onFailedToRecycleView(ViewHolder), onViewAttachedToWindow(ViewHolder) and onViewDetachedFromWindow(ViewHolder) you can override in your AdapterDelegate class.

Then an AnimalAdapter could look like this:

public class AnimalAdapter extends RecyclerView.Adapter {

  private AdapterDelegatesManager<List<Animal>> delegatesManager;
  private List<Animal> items;

  public AnimalAdapter(Activity activity, List<Animal> items) {
    this.items = items;

    delegatesManager = new AdapterDelegatesManager<>();

    // AdapterDelegatesManager internally assigns view types integers
    delegatesManager.addDelegate(new CatAdapterDelegate(activity))
                    .addDelegate(new DogAdapterDelegate(activity))
                    .addDelegate(new GeckoAdapterDelegate(activity));

    // You can explicitly assign integer view type if you want to
    delegatesManager.addDelegate(23, new SnakeAdapterDelegate(activity));
  }

  @Override public int getItemViewType(int position) {
    return delegatesManager.getItemViewType(items, position);
  }

  @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    return delegatesManager.onCreateViewHolder(parent, viewType);
  }

  @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    delegatesManager.onBindViewHolder(items, position, holder);
  }

  @Override public int getItemCount() {
    return items.size();
  }
}

Reducing boilerplate code

As you have seen in the code snippet above this may require to write the same boiler plate code again and again to hook in AdapterDelegatesManager to Adapter. This can be reduced by extending either from ListDelegationAdapter if the data source the adapter displays is java.util.List<?> or AbsDelegationAdapter which is a more general one (not limited to java.util.List)

ListDelegationAdapter

For example the same AnimalAdapter from above could be simplified as follows by extending from ListDelegationAdapter:

public class AnimalAdapter extends ListDelegationAdapter<List<Animal>> {

  public AnimalAdapter(Activity activity, List<Animal> items) {

    // DelegatesManager is a protected Field in ListDelegationAdapter
    delegatesManager.addDelegate(new CatAdapterDelegate(activity))
                    .addDelegate(new DogAdapterDelegate(activity))
                    .addDelegate(new GeckoAdapterDelegate(activity))
                    .addDelegate(23, new SnakeAdapterDelegate(activity));

    // Set the items from super class.
    setItems(items);
  }
}

AbsListItemAdapterDelegate

Also you may have noticed that you often have to write boilerplate code to cast items and ViewHolders when working with list of items as adapters dataset source. AbsListItemAdapterDelegate can help you here. Let's take this class to create a CatListItemAdapterDelegate similar to the CatAdapterDelegate from top of this page but without the code for casting items.

public class CatListItemAdapterDelegate extends AbsListItemAdapterDelegate<Cat, Animal, CatViewHolder> {

  private LayoutInflater inflater;

  public CatAdapterDelegate(Activity activity) {
    inflater = activity.getLayoutInflater();
  }

  @Override public boolean isForViewType(Animal item, List<Animal> items, int position) {
    return item instanceof Cat;
  }

  @Override public CatViewHolder onCreateViewHolder(ViewGroup parent) {
    return new CatViewHolder(inflater.inflate(R.layout.item_cat, parent, false));
  }

  @Override public void onBindViewHolder(Cat item, CatViewHolder vh, @Nullable List<Object> payloads) {
    vh.name.setText(item.getName());
  }

  static class CatViewHolder extends RecyclerView.ViewHolder {

    public TextView name;

    public CatViewHolder(View itemView) {
      super(itemView);
      name = (TextView) itemView.findViewById(R.id.name);
    }
  }
}

As you see, instead of writing code that casts list item to Cat we can use AbsListItemAdapterDelegate to do the same job (by declaring generic types).

DiffUtil & ListAdapter = AsyncListDifferDelegationAdapter

Support library 27.0.1 introduced ListAdapter - the new extension of RecyclerView.Adapter that uses AsyncListDiffer internally. It does calculating diff in the background thread by default and does all particular animations for you items collection. Hence you don't need carry about notify* methods, AsyncListDiffer does all the job for you. And AdapterDelegates supports it too.

This library offers the equivalent to ListAdapter which is called AsyncListDifferDelegationAdapter that can be used together with any regular AdapterDelegate.

public class DiffAdapter extends AsyncListDifferDelegationAdapter<Animal> {
    public DiffAdapter() {
        super(DIFF_CALLBACK) // Your diff callback for diff utils
        delegatesManager
            .addDelegate(new DogAdapterDelegate());
            .addDelegate(new CatAdapterDelegate());
    }
}

Pagination

There is an additional artifact for the pagination library:

implementation 'com.hannesdorfmann:adapterdelegates4-pagination:4.3.2'

Use PagedListDelegationAdapter.

Fallback AdapterDelegate

What if your adapter's data source contains a certain element you don't have registered an AdapterDelegate for? In this case the AdapterDelegateManager will throw an exception at runtime. However, this is not always what you want. You can specify a fallback AdapterDelegate that will be used if no other AdapterDelegate has been found to handle a certain view type.

AdapterDelegate fallbackDelegate = ...;
adapterDelegateManager.setFallbackDelegate( fallbackDelegate );

Note also that boolean return type of isForViewType() of a fallback delegate will be ignored (will not be take into account). So it doesn't matter if you return true or false. You can use AbsFallbackAdapterDelegate that already implements isForViewType() so that you only have to override onCreateViewHolder() and onBindViewHolder() for your fallback adapter delegate.

Version 3.x to 4.0 migration

AdapterDelegates3 uses com.android.support:recyclerview-v7:x.y.z whereas AdapterDelegates4 uses androidx.recyclerview:recyclerview:1.0.0. Migration should be easy. Just use IntelliJ IDE or Android Studio 'Replace in Path' (can be found inside Edit main menu then Find submenu): Replace com.hannesdorfmann.adapterdelegates3 with com.hannesdorfmann.adapterdelegates4. You might also have to replace android.support.v7.widget.RecyclerView with androidx.recyclerview.widget.RecyclerView and android.support.annotation.NonNull with androidx.annotation.NonNull.

License

Copyright 2015 Hannes Dorfmann

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

adapterdelegates's People

Contributors

daviddta avatar edvaldeysteinsson avatar gabrielittner avatar iainconnor avatar igoriols avatar imangazalievm avatar kukuhyoniatmoko avatar mig35 avatar olegkan avatar paulwoitaschek avatar rubenweerts avatar rwliang avatar sab44 avatar smbduknow avatar sockeqwe avatar technoir42 avatar thellmund avatar vanniktech avatar veyndan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

adapterdelegates's Issues

Make parameter payloads @Nullable in onBindViewHolder method in AdapterDelegate and AbsListItemAdapterDelegate classes.

there is a method protected abstract void onBindViewHolder(@NonNull T items, int position, @NonNull RecyclerView.ViewHolder holder, @NonNull List<Object> payloads); in AdapterDelegate class. But the actual value of payloads parameter is null quite often, here is an example callstack f(I'm using AbsListItemAdapterDelegate here):

at com.hannesdorfmann.adapterdelegates3.AbsListItemAdapterDelegate.onBindViewHolder(AbsListItemAdapterDelegate.java:50)
at com.hannesdorfmann.adapterdelegates3.AbsListItemAdapterDelegate.onBindViewHolder(AbsListItemAdapterDelegate.java:41)
at com.hannesdorfmann.adapterdelegates3.AdapterDelegatesManager.onBindViewHolder(AdapterDelegatesManager.java:281)
at com.hannesdorfmann.adapterdelegates3.AbsDelegationAdapter.onBindViewHolder(AbsDelegationAdapter.java:78)

the problem is very severe with Kotlin: when I override this method I have to use non-null syntax for payloads: override fun onBindViewHolder(item: T, viewHolder: RecyclerView.ViewHolder, payloads: MutableList<Any>) so I get java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter payloads at runtime

Currently I have to use a pretty ugly workaround but it's very frustrating

Update Readme: toplevel val is actually not leaking context

As discussed in #69 using a toplevel val in combination with adapterDelegate kotlin dsl is actually not leaking any context. thus toplevel val are fine, however, im not sure if I would consider it as best practice to use toplevel val.

I personally would still prefer a toplevel function over a toplevel val because with a toplevel val a static object is kept for the entire lifetime of a running app. it might be ok if you really use that adapterdelegate in almost all screens (could have a very very very small performance improvement as you donโ€™t create a new object all the time you need that adapterdelegate) but the disadvantage is a slightly higher memory consumption if you dont (very very very little though #microOptimization) because toplevel val is steatic and therefore never garbage collected.

Moreover, dependency injection is only possible with toplevel val

I think the readme should use toplevel functions but also mention that toplevel vals are fine with a link to this issue to get more insights why I would recommend to use toplevel functions instead of toplevel val

Working with SortedList

Hello. Andorid team presenterd sortedList which is really useful when it comes to RecyclerView item ordering.
I wounder if AdapterDelegates will work with sortedList.

AbsAdapterDelegate<T> extension from AnnotatedAdapter

Part 01

After working with the lib right after working on AnnotatedAdapter, I asked myself the question "would there be any merit in having a class that looks something like:

public abstract class AbsAdapterDelegate<T> _extends SupportAnnotatedAdapter_
implements AdapterDelegate<T> {...}

public class Type00AdapterDelegate extends AbsAdapterDelegate<List<DisplayableItem>>
_implements RecyclerListAdapterBinder_{...}

which would generate the
static class Type00ViewHolder extends RecyclerView.ViewHolder

I think yes. The current implementation offers you the ability to use different delegates for different views, thus the model has a complimentary delegate class. To create a different view for the same model, you are required to create a new delegate class.

By detecting the AnnotatedAdapter and combining its functionality with AdapterDelegates, you could now have multiple views displayed for one model, which would be useful for multi form factor development(Tv:largeViews, Wear:imageViews, mobile:ImageView and text, Tablet:large imageView, largeText and detail text...) or based on context(connected to WIFI:load character models into GLSurfaceView, 3G: grab images with Glide..., thematic/stylistic change)

Part 02

After reading your annotations post, Butterknife groups annotation arguments as so:

@Bind({ R.id.first_name, R.id.middle_name, R.id.last_name })
List<EditText> nameViews;

// translates to
@BindDelegates({AdAdapterDelegate, Type00AdapterDelegate, Type01AdapterDelegate})
private AdapterDelegatesManager<List<Viewable>> delegatesManager;

// or generalize with(not so sure about this one via implementation, but conceptually
// one would pass in a class that defines a group of delegates, allowing you to switch
// between them, or even pass in a new grouping)
@BindGroupings({AdapterDelegateGrouping00, AdapterDelegateGrouping01})
private AdapterDelegatesManager<List<Viewable>> delegatesManager;

public void setAdapterDelegate_Group/Single(AdapterDelegateGroup adg){
delegatesManager.setDelegate(adg);
}
public void addAdapterDelegate_Group/Single_ToCurrentSet(...){}

Showing only Reptiles with the Reptile delegate, by requiring a new list, or skipping elements that are not of that subtype.

Conflict with android support v4

I got the error when add AdapterDelegates into my Gradle

Error:Execution failed for task ':comico_client:transformClassesWithJarMergingForAlphaRelease'.

com.android.build.api.transform.TransformException: java.util.zip.ZipException: duplicate entry: android/support/v4/view/PagerTitleStripIcs$SingleLineAllCapsTransform.class

Hand over more lifecycle functions to delegates

Actually only the adapter lifecycle functions for creation and bindings are supported. Would it be an option to add more?

public void onBindViewHolder(@NonNull T items, int position,
      @NonNull RecyclerView.ViewHolder viewHolder)

For example my case would be a callback when the view gets recycled for unbinding presenters.

Funny Animation In Recyclerview

The implementation doesn't work well with recycler view set item animator. Am using a library that does the animation for me. I think it has something to do with notifyDataSetChanged. I might be wrong.

animator library
compile 'jp.wasabeef:recyclerview-animators:1.2.0@aar'

Setting onClick listeners for each card?

Hey,

This is more of an improvement. Setting an onClick seems to be a challenge using this method -- Creating the typedelegate which extends the adapter delegate for each type and then finding the position which the user has clicked for type in the list -- complex for multiple items in the list, i know....but a necessity for a recycler view that does anything more than just display a list of things with some data.

Horizontal inside Vertical layout manager

Hey, nice approach on delegating adapters.

However I have a few issues regarding this design. Is it possible to have two different RecyclerViews in an adapter? How do I go about doing this with the same data set passed into this adapter?

For the same data set, what I mean is according to your sample, you're implementing every related models to an interface class. How do I split this data set to delegate it to different adapter types?

Thanks!

setFallbackDelegate

Hi, first of all, ty for the library. I seen that setFallbackDelegate was removed in 2.0.1 version. What is the motivation? Any workarount to avoid exception if any delegate can control viewtype?

Ty and sorry about my english

Getting Position When Extend it From AbsListItemAdapterDelegate

Hi,

I was extending my Delegates from AbsListItemAdapterDelegate to reduce amount of boilerplate codes, but when I need to delete item from the list I need the exact position. In overrided OnBindView method you don't provide the position, is there any way to get it and delete the item properly ?

Make core classes final

Is there any reason classes like AdapterDelegatesManager are not final? They really do not look like they are built for inheritance.

Diffing

I have a question about the adapter delegates.

How can I perform diffing on the data? Let's say I have a Type A that is always on top of the view and a List of Type B that follows.
Now I want to remove Type A. How do I perform the diffing so that the adapter only calls 'notifyItemChanged' on the first position? Or when I reordered the List?
Normally I use DiffUtil but I'm not sure how to apply that here.

SpanSizeLookup and ItemDecoration

Different SpanSizeLookup's and ItemDecoration's(mostly margins for pre/post-lollipop CardViews) is pretty widespreaded business issue, and as long as these guys are the part of RecyclerView gang bang, we should cover them.

I'd like to contribute, as long as I definitely gonna use your lib.

As of ItemDecoration I'd suggest to build something like:

public class ItemDecorationManager extends RecyclerView.ItemDecoration {
    // ... similar stuff to what you got in AbsDelegationAdapter.class

    @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
        RecyclerView.State state) {
      super.getItemOffsets(outRect, view, parent, state);

      for (ItemDecorationDelegate delegate : delegates) {
        if(delegate.isForViewType(...)) delegate.decorate(....);
      }
    }
  }

  public interface ItemDecorationDelegate<T> {
    void decorate(Rect outRect, View view, RecyclerView parent, RecyclerView.State state);
    boolean isForViewType(T item, int position);
  }
}

and then just:

    ItemDecorationManager manager = new ItemDecorationManager();
    manager.addDelegate(delegate1);
    manager.addDelegate(delegate2);
    manager.addDelegate(delegate3);
    manager.addDelegate(delegate4);

    recyclerView.addItemDecoration(manager);  //or we can call it Wrapper

for SpanSizeLookup I'd follow the same approach.

The con of such approach is that we duplicate isForViewType(), so I'd suggest to decouple that logic.

I'd like to talk about the issue, pick the concept and then contribute!

Cheers!

onBindViewHolder

Hi,
Why methods onBindViewHolder and isForViewType have all items as parameters? We need just current item, not all of them
That is critical because I tried to use your library with Paging library from Architecture Components where i can not get items from adapter

Reuse AdapterDelegate<List<T>> in a domain specific way

Use case:

I've got a few layouts which I can reuse on different screens. Think of an empty screen. Empty screen consists of an Emoji, text and also subtitle.

data class EmptyState(
  val emoji: String,
  val title: String,
  val subtitle: String
)

I want to reuse code for inflation + binding, so I've created:

object AdapterDelegateFactory {
  fun emptyState() = adapterDelegate<EmptyState, EmptyState>(R.layout.adapter_item_empty_state) {
    val emoji by lazy(NONE) { itemView.findViewById<TextView>(R.id.adapterItemEmptyStateEmoji) }
    val title by lazy(NONE) { itemView.findViewById<TextView>(R.id.adapterItemEmptyStateTitle) }
    val subtitle by lazy(NONE) { itemView.findViewById<TextView>(R.id.adapterItemEmptyStateSubtitle) }

    bind {
      emoji.text = item.emoji
      title.text = item.title
      subtitle.text = item.subtitle
    }
  }
}

Now on my feature screen A I have a RecyclerView that can either have Leaderboard entries or an empty state:

sealed class Entry {
  abstract val id: String

  data class LeaderBoardEntry(val leaderBoard: Leaderboard, override val id: String = leaderBoard.id.toString()) : Entry()
  data class EmptyStateEntry(val emptyState: EmptyState, override val id: String = emptyState.hashCode().toString()) : Entry()
}

Creating the AdapterDelegate for the leader board is straight forward:

private val adapterDelegateLeaderBoard: AdapterDelegate<List<Entry>> = adapterDelegate<LeaderBoardEntry, Entry>(R.layout.yatzy_adapter_item_leaderboard) {
  ...
}

Now, how can I reuse the empty state Adapter Delegate?

private val adapter by lazy(NONE) { AsyncListDifferDelegationAdapter(
  diffUtil { it.id.hashCode().toLong() },
  adapterDelegateLeaderBoard,
  AdapterDelegateFactory.emptyState())
}

This does not work since my factory function returns an AdapterDelegate<List<EmptyState>> and I need a AdapterDelegate<List<Entry>>.

This is what I came up with:

private val adapterDelegateEmptyState: AdapterDelegate<List<Entry>> =
  AdapterDelegateFactory.emptyState().wrapped<EmptyState, EmptyStateEntry, Entry> { it.emptyState }

Then I can do:

private val adapter by lazy(NONE) { AsyncListDifferDelegationAdapter(
  diffUtil { it.id.hashCode().toLong() },
  adapterDelegateLeaderBoard,
  adapterDelegateEmptyState
) }

Now as for the wrapped function:

fun <From, To : ToBase, ToBase> AdapterDelegate<List<From>>.wrapped(mapper: (To) -> From): AdapterDelegate<List<ToBase>> {
  val that = this
  @Suppress("UNCHECKED_CAST")
  return object : AdapterDelegate<List<To>>() {
    override fun onCreateViewHolder(parent: ViewGroup) = that.onCreateViewHolder(parent)

    override fun isForViewType(items: List<To>, position: Int): Boolean {
      return that.isForViewType(items.map { mapper(it) }, position)
    }

    override fun onBindViewHolder(items: List<To>, position: Int, holder: ViewHolder, payloads: MutableList<Any>) {
      that.onBindViewHolder(items.map { mapper(it) }, position, holder, payloads)
    }
  } as AdapterDelegate<List<ToBase>>
}

I feel like this functionality should be provided by the library? Maybe it even is and I'm missing it? Additionally, since the methods are protected my helper function currently lives in package com.hannesdorfmann.adapterdelegates4 ๐Ÿ˜†

Do you have any better ideas?

adapterDelegateViewBinding doesn't work for ViewDataBinding

Hello,
I tried to implement the adapter delegate using dataBinding but as a result I've got the following issue :

  • I used adapterDelegateViewBinding to create a new adapterDelegate but it uses only ViewBinding, therefore ViewDataBinding is excluded.

Paging library

Great work with this lib ๐Ÿ‘

Any changes this it will provide compatibility with Android Paging Library from the architecture components?

[Crash] Null datasource when PagedList is null

Currently we're seeing spurious crashes when using the PagedListDelegationAdapter

How to reproduce

Unclear, seems to happen when initialising a new adapter

Affected devices

All tested, some Pixels, some Samsung Galaxys

Version of library

4.2.0

Stacktrace

Relevant portion:

2019-11-03 15:21:28.783 ? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: at.radio.android, PID: 32618
    java.lang.NullPointerException: Items datasource is null!
        at com.hannesdorfmann.adapterdelegates4.AdapterDelegatesManager.getItemViewType(AdapterDelegatesManager.java:232)
        at com.hannesdorfmann.adapterdelegates4.paging.PagedListDelegationAdapter.getItemViewType(PagedListDelegationAdapter.java:93)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5926)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
        at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
        at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
        at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
        at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
...

Full stacktrace: https://gist.github.com/avalanchas/21f1965ed3ab425d05753bda1ff8fc43

As I read the Android docs, it's a general truth, that the getCurrentList method of a PagedList can be null at any point. So isn't it just the case, that we'll need to change this:

@Override
public int getItemViewType(int position) {
    return delegatesManager.getItemViewType(getCurrentList(), position);
}

to this:

@Override
public int getItemViewType(int position) {
    return getCurrentList() == null ? FALLBACK : delegatesManager.getItemViewType(getCurrentList(), position);
}

Paging library not working with PagedListDelegationAdapter

Hi,I have implemented paging library and I am using the PagedListDelegationAdapter. But the datasource's loadAfter method is not being called and I think the reason is the delegate adapter is not calling getItem() function which I guess kicks in the loadAfter function in datasource. Any help is appreciated.

How to mock AsyncListDifferDelegationAdapter

Hi, I have class extending AsyncListDifferDelegationAdapter and want to write unit tests for it. Problem is that is fails with java.lang.ExceptionInInitializerError, Method getMainLooper in android.os.Looper not mocked.

How do I mock the async stuff so I can write unit tests? I'm using com.nhaarman.mockitokotlin2.

Stacktrace:
java.lang.ExceptionInInitializerError
at com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter.(AsyncListDifferDelegationAdapter.java:60)
at com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter.(AsyncListDifferDelegationAdapter.java:47)
at com.netsuite.nsforandroid.core.collection.ui.view.RecyclerCollectionAdapter.(RecyclerCollectionAdapter.kt:27)
at com.netsuite.nsforandroid.core.collection.ui.view.RecyclerCollectionAdapter.(RecyclerCollectionAdapter.kt:26)
at com.netsuite.nsforandroid.core.collection.ui.view.RecyclerCollectionAdapter$Companion.invoke(RecyclerCollectionAdapter.kt:108)
at com.netsuite.nsforandroid.core.collection.ui.view.RecyclerCollectionAdapterTest.(RecyclerCollectionAdapterTest.kt:14)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.junit.runners.BlockJUnit4ClassRunner.createTest(BlockJUnit4ClassRunner.java:217)
at org.junit.runners.BlockJUnit4ClassRunner$1.runReflectiveCall(BlockJUnit4ClassRunner.java:266)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.BlockJUnit4ClassRunner.methodBlock(BlockJUnit4ClassRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

AsyncListDiffer IndexOutOfBoundsException: Index: 1, Size: 21

Support Lib: 27.1.1

Library used: Hannesdorfmann Adapterdelegates3 (https://github.com/sockeqwe/AdapterDelegates),
AsyncListDifferDelegationAdapter

Version: com.hannesdorfmann:adapterdelegates3:3.1.0

Setup:

A FragmentStatePagerAdapter with 3 Fragments containing a RecyclerView with an instance of AsyncListDifferDelegationAdapter.

List of data is emitted to listing fragment using Viewmodel and Livedata,

The Livedata is observed using observeForever(due to business rule) and subscribed in OnViewCreated and unsubscribed on onDestroyView() in the listing fragment.

Issue:

Randomly during updating recyclerview list, when new list is submitted to the AsyncListDiffer,
IndexOutOfBoundsException is thrrown inside android.support.v7.recyclerview.extensions.AsyncListDiffer without stacktrace of the fragment in which it was used, Also the exception thrown is incorrect and crashing the app.

Device:

Samsung Galaxy J7 Prime(SM-G610F)
OS: Android 8.1.0
Samsung experience version: 9.5

Please help with any workarounds or any idea how to handle the exception or fix it.

StackTrace:
03-20 16:10:16.016 26291 26518 E AndroidRuntime: java.lang.IndexOutOfBoundsException: Index: 1, Size: 21
03-20 16:10:16.016 26291 26518 E AndroidRuntime: at java.util.ArrayList.get(ArrayList.java:437)
03-20 16:10:16.016 26291 26518 E AndroidRuntime: at android.support.v7.recyclerview.extensions.AsyncListDiffer$1$1.areItemsTheSame(AsyncListDiffer.java:239)
03-20 16:10:16.016 26291 26518 E AndroidRuntime: at android.support.v7.util.DiffUtil.diffPartial(DiffUtil.java:224)
03-20 16:10:16.016 26291 26518 E AndroidRuntime: at android.support.v7.util.DiffUtil.calculateDiff(DiffUtil.java:136)
03-20 16:10:16.016 26291 26518 E AndroidRuntime: at android.support.v7.util.DiffUtil.calculateDiff(DiffUtil.java:97)
03-20 16:10:16.016 26291 26518 E AndroidRuntime: at android.support.v7.recyclerview.extensions.AsyncListDiffer$1.run(AsyncListDiffer.java:225)
03-20 16:10:16.016 26291 26518 E AndroidRuntime: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
03-20 16:10:16.016 26291 26518 E AndroidRuntime: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
03-20 16:10:16.016 26291 26518 E AndroidRuntime: at java.lang.Thread.run(Thread.java:764)

56.318 20749 20948 E AndroidRuntime: Process: com.shaadi.android, PID: 20749
03-20 15:31:56.318 20749 20948 E AndroidRuntime: java.lang.IndexOutOfBoundsException: Index: 4, Size: 21
03-20 15:31:56.318 20749 20948 E AndroidRuntime: at java.util.ArrayList.get(ArrayList.java:437)
03-20 15:31:56.318 20749 20948 E AndroidRuntime: at android.support.v7.recyclerview.extensions.AsyncListDiffer$1$1.areItemsTheSame(AsyncListDiffer.java:239)
03-20 15:31:56.318 20749 20948 E AndroidRuntime: at android.support.v7.util.DiffUtil.diffPartial(DiffUtil.java:259)
03-20 15:31:56.318 20749 20948 E AndroidRuntime: at android.support.v7.util.DiffUtil.calculateDiff(DiffUtil.java:136)
03-20 15:31:56.318 20749 20948 E AndroidRuntime: at android.support.v7.util.DiffUtil.calculateDiff(DiffUtil.java:97)
03-20 15:31:56.318 20749 20948 E AndroidRuntime: at android.support.v7.recyclerview.extensions.AsyncListDiffer$1.run(AsyncListDiffer.java:225)
03-20 15:31:56.318 20749 20948 E AndroidRuntime: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
03-20 15:31:56.318 20749 20948 E AndroidRuntime: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
03-20 15:31:56.318 20749 20948 E AndroidRuntime: at java.lang.Thread.run(Thread.java:764)
03-20 15:31:56.439 18411 18445 E PBSessionCacheImpl: sessionId[80973987391565932] not persisted.

Is this possible to all delegate adapter has pagingadapter

Hi, I want to build app like Google Play or Netflix style app. Each section has paging ability, like Popular Movies section, Featured Movies section etc. and also each section has horizontal orientation with paging feature.. Is this possible to make build this kind of app with using your AdapterDelegates. Because, I have checked your sample app all section has vertical.

Add support for DiffUtils

We could leverage the power of kotlin DSL even more to add support for DiffUtils. Something like that:

fun catAdapterDelegate() = adapterDelegate<Cat, Animal>(R.layout.item_cat) {

    val name : TextView = findViewById(R.id.name)
   
    bind { diffPayloads -> // diffPayloads is a List<Any> containing the Payload from your DiffUtils
        name.text = item.name // Item is of type Cat and is the current bound item.
    }

   diff {
      itemsTheSame { old, new -> old == new }   // returns true if they are the same
      contentTheSame {
         same { old, new -> false }             // returns true if content is the same
         changePayload { old, new -> ... }      // returns the payload
      }
   }
}

Different objects for Delegates

Hey there ;) I tried using this library for the following RecyclerView scheme and found it a bit cumbersome to work with it. The scheme basically looks like this:

Custom Widget (No data needed)
Divider (No data needed)
Header (String needed)
Member 1 (Member needed)
Member 2 (Member needed)
Member 3 (Member needed)
...
Member X (Member needed)
Add Member Button (No data needed)
Divider (No data needed)
Simple View displayed (No data needed)
Divider (No data needed)
Header with right button (Strings needed)
Nested View (Data needed (List<Data))

Via the Add Member it's possible to add members. If you have added members there will be shown another Divider and Layout underneath the Add Member button.

Also for every view type that has No data needed, createView would be enough since I won't be doing anything in onBindViewHolder. I just want to inflate my layout and show it.

I could try to put everything in one List of type Object and then create some Divider and Header objects since I'd need something to satisfy the isForViewType method but I'm really against List<Object as it's error prone.

It might also be that this type of RecyclerView scheme is just too complex. Any ideas?

Adapter Methods

Hello,

I have some adapter methods in which I almost repeat for all adapters. Where should it be so I don't have to repeat but also can enable or disable which feature I want in individual adapters.

Or I should just create an interface, extend the recycler view method. And use super and override to disable and enable functionality?

public void add(Object item) {
list.add(item);
}

public void addItem(Object item) {
    add(item);
    notifyDataSetChanged();
}

public void removeItem(int position) {
    list.remove(position);
    notifyItemRemoved(position);
}

public void clearItems() {
    list.clear();
} 

Method onBindViewHolder(RecyclerView.ViewHolder holder, int position) is never called

Hi. After the issue #21 has been resolved, a 2 arg method will be never called by Android SDK, because we have already an overridden 3 arg method

public abstract class AbsDelegationAdapter<T> extends RecyclerView.Adapter {
//...
  @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    delegatesManager.onBindViewHolder(items, position, holder, null);
  }

  @Override
  public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {
    delegatesManager.onBindViewHolder(items, position, holder, payloads);
  }
//...
}

So I can override this 2 arg method in my adapter and get no results. It's quite strange.

Remove manually assignment of ViewType int

Should be very easy that the AdapterDelegatesManager internally assignes ViewType integers.

The only question is if we should do that in 1.x by simply adding a AdapterDelegate.setViewType(int) method

or

introducing that feature in 2.0 which may break backward compatibility ...

The cleaner solution would be to do it in 2.0 by removing AdapterDelegate.getViewType() and let AdapterDelegatesManager internally assign int viewType.

mention androidExtensions experimental=true in readme for LayoutContainer

Hi Hannes,

please include an note in the readme for kotlin-dsl-layoutcontainer to add

androidExtensions {
    experimental = true
}

in build.gradle (app).

I spent 3 days figuring this out, why i get null-exceptions at runtime in the ListDelegationAdapter, while studio doesn't complain.

Thank you.

Delegates hierarchy and reusability

Hi! I try to use adapter delegates for building reusable widgets.
For example, I have several buttons, which have some differences in width, height, background, and maybe some other UI options. Also, these widgets have lots of common logic. What appropriate way to create some hierarchy and avoid copy past in that case?

Lint Rule that checks that no top level property uses kotlin DSL

the new kotlin DSL is nice but could lead to memory leaks if you use a top level val in a kotlin file as such a top level val will internally result in a static field that will be kept forever. Thus, the context of the adapter delegate might be kept forever around (leaking activity).

Adapter not showing items when using DiffUtil

Hi,

I've been playing around with DiffUtil and AdapterDelegates but can't seem to show the items using those two. However, running DIffUtil with "raw" RecyclerView.Adapter works fine. Here is a snippet of the code I am using:

Adapter

class RewardListAdapter(manager: AdapterDelegatesManager<List<RewardViewModel>>) : 
            ListDelegationAdapter<List<RewardViewModel>>(manager)

Delegate

    class RewardAdapterDelegate(private val inflater: LayoutInflater,
                                private val removeSubject: PublishSubject<RemoveRewardFromListUseCase.Parameters>) : AdapterDelegate<List<RewardViewModel>>() {

        override fun onBindViewHolder(items: List<RewardViewModel>, position: Int, holder: RecyclerView.ViewHolder, payloads: MutableList<Any>) {
            val vh = holder as RewardViewHolder
            val reward = items[position]
            vh.bindReward(reward, items)
        }

        override fun isForViewType(items: List<RewardViewModel>, position: Int): Boolean = true

        override fun onCreateViewHolder(parent: ViewGroup?): RecyclerView.ViewHolder =
            RewardViewHolder(inflater.inflate(R.layout.item_reward, parent, false))


        inner class RewardViewHolder(view: View) : RecyclerView.ViewHolder(view) {

            fun bindReward(rewardView: RewardViewModel, items: List<RewardViewModel>) {
                with(rewardView) {
                    RxView.clicks(itemView.delete)
                        .map { RemoveRewardFromListUseCase.Parameters(items, rewardView) }
                        .subscribe(removeSubject)
                    itemView.name.text = name
                    itemView.description.text = description
                }
            }
        }

    }

Setting up

val delegatesManager = AdapterDelegatesManager<List<RewardViewModel>>()
            .addDelegate(RewardAdapterDelegate(LayoutInflater.from(activity), removeRewardSubject))

adapter = RewardListAdapter(delegatesManager)
rewardList.adapter = adapter

Updating with new data

val oldList = adapter.items?.toMutableList() ?: mutableListOf()
val newList = state.rewards.toMutableList()

val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {

            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
                        oldList[oldItemPosition].id == newList[newItemPosition].id

            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
                        oldList[oldItemPosition] == newList[newItemPosition]

            override fun getOldListSize() = oldList.size

            override fun getNewListSize() = newList.size
})
diff.dispatchUpdatesTo(adapter)

After the call to dispatchUpdatesTo nothing happens. The list is blank.

For the "raw" adapter I use the approach as defined here:

https://github.com/antoniolg/diffutil-recyclerview-kotlin/blob/master/app/src/main/java/com/antonioleiva/diffutilkotlin/AutoUpdatableAdapter.kt

and my adapter looks like this:

class RewardListAdapter(val deleteSubject: PublishSubject<RemoveRewardFromListUseCase.Parameters>) : 
RecyclerView.Adapter<RewardListAdapter.RewardViewHolder>(), AutoUpdatableAdapter {

        var rewardList: MutableList<RewardViewModel> by Delegates.observable(mutableListOf()) { _, old, new ->
            autoNotify(old, new) { o, n -> o.id == n.id }
        }

        override fun getItemCount(): Int = rewardList.size

        override fun onBindViewHolder(holder: RewardViewHolder, position: Int) {
            val post = rewardList[position]
            holder.bind(post)
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RewardViewHolder =
            RewardViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_reward, parent, false))

        inner class RewardViewHolder(view: View) : RecyclerView.ViewHolder(view) {

            fun bind(rewardView: RewardViewModel) {
                with(rewardView) {
                    RxView.clicks(itemView.delete)
                        .map { RemoveRewardFromListUseCase.Parameters(rewardList, rewardView) }
                        .subscribe(deleteSubject)
                    itemView.name.text = name
                    itemView.description.text = description
                }
            }
        }
    }

Running somewhat similar code using a "raw" adapter works just fine. Any thoughts? Am I doing something wrong?

Thanks for the awesome lib!

[CRASH] adapterDelegateViewBinding's block crashing

I'm experiencing a crash when using adapterDelegateViewBinding

The code I use :

val adapter = ListDelegationAdapter<List<ViewAllItem>>(
        comicAdapterDelegate()
    )

    private fun comicAdapterDelegate() =
        adapterDelegateViewBinding<ViewAllItem.Comic, ViewAllItem, ComicOverviewItemBinding>(
            viewBinding = { layoutInflater, root ->
                ComicOverviewItemBinding.inflate(
                    layoutInflater,
                    root,
                    false
                )
            }
        ) {
            binding.title.text = item.comic.title
            binding.subTitle.text = item.comic.dates[0].toString()
            binding.image.load(ImageUtils.formatImage(item.comic.thumbnail)) {
                crossfade(true)
                placeholder(R.drawable.placeholder)
            }
        }
...
binding?.recyclerView?.adapter = adapter.apply { items = emptyList() }

I update the items later once I fetch the content from an api :

adapter.items = adapter.items + state.items
adapter.notifyDataSetChanged()

This result in the following crash :

    java.lang.IllegalArgumentException: Item has not been set yet. That is an internal issue. Please report at https://github.com/sockeqwe/AdapterDelegates
        at com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewBindingViewHolder.getItem(ViewBindingListAdapterDelegateDsl.kt:132)
        at com.jeantuffier.inorder.inorder_android.screen.viewall.ViewAllViewModel$comicAdapterDelegate$2.invoke(ViewAllViewModel.kt:75)
        at com.jeantuffier.inorder.inorder_android.screen.viewall.ViewAllViewModel$comicAdapterDelegate$2.invoke(ViewAllViewModel.kt:22)
        at com.hannesdorfmann.adapterdelegates4.dsl.DslViewBindingListAdapterDelegate.onCreateViewHolder(ViewBindingListAdapterDelegateDsl.kt:63)
        at com.hannesdorfmann.adapterdelegates4.dsl.DslViewBindingListAdapterDelegate.onCreateViewHolder(ViewBindingListAdapterDelegateDsl.kt:47)
        at com.hannesdorfmann.adapterdelegates4.AdapterDelegatesManager.onCreateViewHolder(AdapterDelegatesManager.java:267)
        at com.hannesdorfmann.adapterdelegates4.AbsDelegationAdapter.onCreateViewHolder(AbsDelegationAdapter.java:88)
        at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:7078)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6235)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
        at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
        at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1627)
        at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
        at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
        at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
        at android.view.View.layout(View.java:23753)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at com.google.android.material.appbar.HeaderScrollingViewBehavior.layoutChild(HeaderScrollingViewBehavior.java:148)
        at com.google.android.material.appbar.ViewOffsetBehavior.onLayoutChild(ViewOffsetBehavior.java:43)
        at com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior.onLayoutChild(AppBarLayout.java:1996)
        at androidx.coordinatorlayout.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:918)
        at android.view.View.layout(View.java:23753)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at android.view.View.layout(View.java:23753)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1695)
        at android.view.View.layout(View.java:23753)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at android.view.View.layout(View.java:23753)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
        at android.view.View.layout(View.java:23753)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at android.view.View.layout(View.java:23753)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
2020-08-05 21:35:13.318 30468-30468/com.jeantuffier.inorder.inorder_android E/AndroidRuntime:     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
        at android.view.View.layout(View.java:23753)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at com.android.internal.policy.DecorView.onLayout(DecorView.java:1062)
        at android.view.View.layout(View.java:23753)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3679)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3139)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2200)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9065)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:999)
        at android.view.Choreographer.doCallbacks(Choreographer.java:797)
        at android.view.Choreographer.doFrame(Choreographer.java:732)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:984)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:237)
        at android.app.ActivityThread.main(ActivityThread.java:8016)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1076)
2020-08-05 21:35:13.348 30468-30468/com.jeantuffier.inorder.inorder_android I/Process: Sending signal. PID: 30468 SIG: 9

If I comment out :

binding.title.text = item.comic.title
            binding.subTitle.text = item.comic.dates[0].toString()
            binding.image.load(ImageUtils.formatImage(item.comic.thumbnail)) {
                crossfade(true)
                placeholder(R.drawable.placeholder)
            }

Then I do not get any crash

Testing onCreateView/onBindView with Robolectric

Hey!
First of all, thank you very much for the library. It is a very smart and useful thing to work with, and it just works great!

Currently I need to test a RecyclerView whose adapter uses AdapterDelegates for setting up different types of adapters for several view types, and I would do this by using Robolectric.

The way I would test is the following, as if I was using any ordinary RecyclerView.Adapter:

Let's say I have an adapter:

myCoolAdapter = new CoolAdapter(this,
                allVehicles,
                getSupportFragmentManager(),
                accountListener,
                confirmedListener,
                connectedListener,
                unconnectedListener);

And this adapter is defined as follows:

public class CoolAdapter extends AbsDelegationAdapter<List<Pair<Boolean, List<?>>>> {
    private final HeaderDelegate headerDelegate;

    public CoolAdapter (Context context, List<Pair<Boolean, List<?>>> connectedVehicles,
                                        FragmentManager fragmentManager,
                                        HeaderDelegate.AccountListener accountListener,
                                        ConfirmedDelegate.ConfirmedListener confirmedVehicleItemListener,
                                        ConnectedDelegate.ConnectedListener connectedListener,
                                        UnconnectedDelegate.UnconnectedListener unconnectedListener) {
        // We make if a field, so we can have access to the current position of the ViewPager
        headerDelegate = new HeaderDelegate(accountChangedListener, fragmentManager);
        delegatesManager.addDelegate(headerDelegate);
        delegatesManager.addDelegate(new GroupTitleDelegate());
        delegatesManager.addDelegate(new ConnectedDelegate(context, connectedListener));
        delegatesManager.addDelegate(new ConfirmedDelegate(context, confirmedListener));
        delegatesManager.addDelegate(new UnconnectedDelegate(unconnectedListener));
        delegatesManager.addDelegate(new GroupTitleDelegate());
        setItems(connectedVehicles);
    }
...
}

Now let's say that I want to test that my CoolAdapter in this way:

ConnectedDelegate.ConnectedViewHolder connectedViewHolder =
                (ConnectedDelegate.ConnectedViewHolder) myCoolAdapter.onCreateViewHolder(myRecyclerView, myCoolAdapter.getItemViewType(6));
        myCoolAdapter.onBindViewHolder(connectedVehicleViewHolder, 6);

Knowing that the item at position 6 for this viewType should be of type ConnectedViewHolder works perfectly. The problem comes with the following code:

myCoolAdapter.onBindViewHolder(connectedViewHolder, 6);

Even tho I see that connectedViewHolder is correctly retrieved from the delegates, the fields mPosition, mItemId , mItemViewType are all -1, and mOwnerRecycerView are null.
Is there any way to overcome this? How come this happens?

Thanks a lot for the help and advance.

Add sources to release

It would be great if the releases contain the source.
Right now it's difficult to use because there is no JavaDoc, and all the params are random ( protected abstract void onBindViewHolder(@NonNull T var1, int var2, @NonNull ViewHolder var3, @NonNull List<Object> var4);

Nested Recycler View question

Hi there,

My question is regarding a nested Recycler view. Basically I have a PagedList of UserDisplay that is coming from my Mosby MVI Presenter composed with two objects:

data class HeaderUserManagement(var header:String):UserDisplay
data class ParentUser(var userType:List<UserFirebaseStorage>):UserDisplay

I want to display the userType List into a child Recycler view(himself inside a cardview) inside the parent Recycler view(left picture below)

I then created my adapters delegate as follow:

fun headerUserManagementAdapterDelegate() =
    adapterDelegateLayoutContainer<HeaderUserManagement, UserDisplay>(R.layout.user_header_item) {

        bind {
            //Log.d("testRecyclerHeader", itemViewType.toString())
            tvHeaderUser.text = item.header
        }
    }

fun parentUserManagementAdapterDelegate(adapterChild: ListDelegationAdapter<List<ChildDisplay>>) =
    adapterDelegateLayoutContainer<ParentUser, UserDisplay>(R.layout.item_user_test) {

        bind {
            //Log.d("testRecyclerParent", itemViewType.toString())
            //Log.d("testRecyclerParent", item.userType.toString())
            val linearLayoutManager = LinearLayoutManager(this.context)
            rvNested.layoutManager = linearLayoutManager   
rvNested.addItemDecoration(DividerItemDecoration(this.context,LinearLayoutManager.VERTICAL))
            adapterChild.items = item.userType
            rvNested.adapter = adapterChild
        }
    }

fun userFirebaseStorageAdapterDelegate(uFSClickedListener:(UserFirebaseStorage)->Unit) =
    adapterDelegateLayoutContainer<UserFirebaseStorage, ChildDisplay>(R.layout.item_user) {

        mcvUserRV.setOnClickListener { uFSClickedListener(item) }

        bind {
            tvUserName.text = item.userFirebase.name
            tvUserCreditCard.text = item.userFirebase.creditCard
            tvUserEmail.text = item.userFirebase.email

        }
    }

And then my delegates manager and PagesListDelegationAdapter

private val delegatesManager = AdapterDelegatesManager<List<UserDisplay>>()
        .addDelegate(headerUserManagementAdapterDelegate())
        .addDelegate( parentUserManagementAdapterDelegate(
            ListDelegationAdapter(
                userFirebaseStorageAdapterDelegate { userFirebaseStorage: UserFirebaseStorage ->
                    uFSClicked(
                        userFirebaseStorage
                    )
                })
        ))

private val adapterPaged = PagedListDelegationAdapter(delegatesManager,callback)

recycler view init:

 rvAdminUserManagement.apply {
            val llm = LinearLayoutManager(this.context)
            layoutManager = llm
            adapter = adapterPaged
            setOnScrollChangeListener { _, _, _, _, _ ->

                mcvToolbarUserManagement.isSelected = this.canScrollVertically(-1)

            }
        }

So this is working fine and i have my list in a second Recycler View inside a card view as wanted(left picture below). However I also have a bottomSheet with an edit text inside in this fragment. When I open the bottom sheet and type inside the edit text and collapse the bottom sheet the Child Recycler view changed(right picture below). The last item of the second nested recycler view is now visible on the first nested recycler view... Logcat is completely empty and the Parent and child Recycler views are not redrawn.

Error recycler view

My first question would be am I doing everything as it should be done with the library to display nested recycler view? If yes why i have this weird behavior?
Thank you for reading me and hope somebody can be of any help.

Item Selection

How do you handle item selection in view holder on click event? I want to be able to select and deselect an item. Should I go with looping through?

Loading more view

Thank you for the great library. Can you provide an example about how to add and remove loading more view?

Thanks for this

This is very informative. I had always hated the way I was handling my views in my recycler view. I couldn't have solved the problem any better. Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.