A lightweight and easy to use abstraction for Android RecyclerViews.
@Adapter
class EmojiListItemAdapter @Inject constructor() : AnotherAdapter<EmojiListItemViewModel>() {
override val viewType: ViewType = AdapterViewType.from(EmojiListItemAdapter::class.java)
override fun onHandlesItem(item: Any): Boolean = item is EmojiListItemViewModel
override fun onCreateView(parent: ViewGroup, inflater: LayoutInflater, viewType: ViewType): View =
inflater.inflate(R.layout.adapter_emoji_list_item, parent, false)
override fun View.onBindItem(item: EmojiListItemViewModel, position: Int) {
adapterEmojiWidget?.emojiViewModel = item.viewModel
}
}The library is provided through Bintray. Refer to the releases for the latest version.
repositories {
maven {
url = uri("https://dl.bintray.com/chrynan/chrynan")
}
}Android Library:
implementation 'com.chrynan.aaaah:aaaah-libraryx:VERSION'Core Common (Kotlin Multi-platform Classes):
implementation 'com.chrynan.aaaah:aaaah-core:VERSION'Annotations (Optional):
implementation 'com.chrynan.aaaah:aaaah-annotation:VERSION'Annotation Processor (Optional):
kapt 'com.chrynan.aaaah:aaaah-compiler:VERSION'DSL (Optional):
implementation 'com.chrynan.aaaah:aaaah-dsl:VERSION'- Create an
AnotherAdapterimplementation:
class MyAdapter : AnotherAdapter<MyItem>() {
override val viewType = AdapterViewType.from(this::class.java)
override fun onHandlesItem(item: Any) = item is MyItem
override fun onCreateView(parent: ViewGroup, inflater: LayoutInflater, viewType: ViewType): View =
inflater.inflate(R.layout.my_adapter_layout_file, parent, false)
override fun View.onBindItem(item: MyItem, position: Int) {
// Bind the Item to the View
}
}- Create the
ManagerRecyclerViewAdapterwhich handles the coordination between multipleAnotherAdapters:
val managerAdapter = anotherAdapterManager(MyAdapter()) // vararg parameters- Add the
ManagerRecyclerViewAdapterto theRecyclerView:
recyclerView?.apply {
adapter = managerAdapter
layoutManager = LinearLayoutManager(context) // Or whatever LayoutManager needed
}There is a convenience function which is a shortened syntax to create an Adapter. This could be useful for quick basic adapters.
val myAdapter =
anotherAdapter<ItemType>(viewType = myViewType, viewResId = R.layout.my_adapter_layout) { item, position ->
// this refers to the containing Android View
this.findViewById<TextView>(R.id.myTextView)?.text = item.title
}Then to assign the Adapter to a RecyclerView, wrap it in a ManagerRecyclerViewAdapter:
recyclerView?.apply {
adapter = anotherManagerAdapter(myAdapter)
layoutManager = LinearLayoutManager(context) // Or whatever LayoutManager needed
}If only one Adapter is needed, there's no need to explicitly wrap the Adapter, just call the RecyclerView.adapter()
extension function:
recyclerView?.apply {
adapter(myAdapter)
layoutManager = LinearLayoutManager(context) // Or whatever LayoutManager needed
}By default, the AdapterViewType.from() function returns the Hash Code of the Class. For most use cases this should
be sufficient. However, if guaranteed unique View Types are needed for each Adapter, the Annotation Processor could be
used.
- Annotate the Adapter with the
Adapterannotation (available in the Annotations library):
@Adapter
class MyAdapter : AnotherAdapter<MyItem>() {
override val viewType = AdapterViewType.from(this::class.java)
override fun onHandlesItem(item: Any) = item is MyItem
override fun onCreateView(parent: ViewGroup, inflater: LayoutInflater, viewType: ViewType): View =
inflater.inflate(R.layout.my_adapter_layout_file, parent, false)
override fun View.onBindItem(item: MyItem, position: Int) {
// Bind the Item to the View
}
}- Build the project (make sure to have both the compiler and annotations libraries in the dependencies)
A class, AdapterViewTypes, will be generated with View Type constants for each class annotated with the Adapter
annotation. Also, a more specific AdapterViewType.from() extension function will be generated. Either approach could
be used to access the View Type for each adapter:
AdapterViewTypes.MY_ADAPTER
// or
AdapterViewType.from(MyAdapter::class.java)The generated constant names can be overridden by providing a value to the name parameter in the Adapter annotation:
@Adapter(name = "MyConstantName")
// Results in:
AdapterViewTypes.MyConstantNameThe library comes with a DiffUtilCalculator class which is a basic implementation of
RecyclerView's DiffUtil.Callback for a UniqueAdapterItem. This class can be used for most cases but if additional
functionality is needed, the class is extensible. The DiffUtilCalculator.calculateDiff() function takes in a parameter
of list items and calculates the diff and returns an AndroidDiffResult which is a wrapper around the
RecyclerView's DiffUtil.DiffResult.
val diffCalculator = DiffUtilCalculator<UniqueAdapterItem>()
val result = diffCalculator.calculateDiff(sortedItems = myNewListItems)
// myListUpdater is an implementation of the ItemListUpdater interface
myListUpdater.items = result.items
result.diffUtilResult.dispatchItemsTo(myListUpdater)The library contains a DiffProcessor interface in the Kotlin common core module and an AndroidDiffProcessor
implementation which encapsulates the processing logic.
val processor = AndroidDiffProcessor(DiffUtilCalculator())
val result = processor.processDiff(myNewListItems)The library also contains a DiffDispatcher interface in the Kotlin common core module and an AndroidDiffDispatcher
implementation which encapsulates the dispatching logic.
val dispatcher = AndroidDiffDispatcher(myItemListUpdater)
dispatcher.dispatchDiff(result)Both the DiffProcessor.processDiff and the DiffDispatcher.dispatchDiff functions are suspending functions. This is
because these tasks should be performed off the UI Thread.
Using the provided AdapterItemHandler interface, processing items is much easier on a Kotlin Coroutine Flow of
items:
val adapterItemHandler = BaseAdapterItemHandler(myDiffProcessor, myDiffDispatcher)
flowOf(myItems)
.calculateAndDispatcherDiff(adapterItemHandler)
.launchIn(this)Sometimes dynamic Adapter creation is necessary, such as when using nested adapters. For this scenario, there is
the AdapterFactory interface along with the BaseAdapterFactory abstract class implementation. Creating a
custom AdapterFactory implementation is fairly straightforward:
class MyItemAdapterFactory @Inject constructor(
myItemAdapter: MyItemAdapter,
context: Context
) : BaseAdapterFactory<ListItemViewModel>() {
override val adapters: Set<AnotherAdapter<*>> = setOf(myItemAdapter)
override val positionManager: AdapterPositionManager = VerticalPositionManager(context)
}Then the AdapterFactory has to be bound to the RecyclerView, and finally it can be used instead of
the AdapterItemHandler to calculate and dispatch the diffs:
recyclerView?.bindAdapterFactory(myAdapterFactory)
flowOf(myItems)
.calculateAndDispatcherDiff(myAdapterFactory)
.launchIn(this)