sockeqwe / adapterdelegates Goto Github PK
View Code? Open in Web Editor NEW"Favor composition over inheritance" for RecyclerView Adapters
Home Page: http://hannesdorfmann.com/android/adapter-delegates
License: Apache License 2.0
"Favor composition over inheritance" for RecyclerView Adapters
Home Page: http://hannesdorfmann.com/android/adapter-delegates
License: Apache License 2.0
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?
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).
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'
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)
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.
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?
I think that the view type for the fallback delegate should be changed to public, makes sense if a developer wants to take actions based on view types being rendered
Hi,
Do you have interest for adding Feature for Expanding View Group. I can make implementation what your opinion about that ?
Great work with this lib 👏
Any changes this it will provide compatibility with Android Paging Library from the architecture components?
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
}
}
}
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
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.
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.
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
I don't see any LICENSE file, it would be helpful if there was one so it's clear where we can use the code.
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?
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.
there is a typo in ˋinitializerblockˋ
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
Is there any reason classes like AdapterDelegatesManager
are not final? They really do not look like they are built for inheritance.
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.
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
Hi,
Paging 3 is a complete rework of previous paging solution by Google.
Adapter delegates have support for version 2.
Would be good to have support for version 3 as well.
Though version 3 is just announced and is currently in alpha, still would be good to have some initial support.
https://developer.android.com/topic/libraries/architecture/paging/v3-overview
Thank you
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 ?
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
Why _$_findCachedViewById is not used in bind method
Thank you for the great library. Can you provide an example about how to add and remove loading more view?
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:
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!
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();
}
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.
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
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.
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);
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.
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
.
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
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)
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.
Currently we're seeing spurious crashes when using the PagedListDelegationAdapter
Unclear, seems to happen when initialising a new adapter
All tested, some Pixels, some Samsung Galaxys
4.2.0
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);
}
Need position as parameter inside AbsListItemAdapterDelegate.onBindViewHolder
for example :
public abstract void onBindViewHolder(@NonNull VH holder, int position);
Hello,
I tried to implement the adapter delegate using dataBinding but as a result I've got the following issue :
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!
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.
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.
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.
Hello. Andorid team presenterd sortedList which is really useful when it comes to RecyclerView item ordering.
I wounder if AdapterDelegates will work with sortedList.
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!
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?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.