How can I create a sectioned recyclerview in Kotlin? - android

How can I create a sectioned recyclerview in Kotlin based on list, and sections need to be grouped by one of the subtype of Data model?

You can have your ViewHolder be a sealed class with one child for each type of view you wanna display (subtypeA, subtypeB, maybe divider etc)
Then in your adapter you create the correct viewholder based on the viewType
override fun getItemViewType(position: Int): Int = when (getItem(position)) {
is XXXUIModel.SubTypeA -> SUBTYPE_A
is XXXUIModel.SubTypeB -> SUBTYPE_B
is XXXUIModel.Divider -> DIVIDER
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int,
): XXXAdapterViewHolder = when (viewType) {
SUBTYPE_A -> XXXViewHolder.SubYypeA
SUBTYPE_B -> XXXViewHolder.SubYypeB
DIVIDER -> XXXViewHolder.Divider
....

Related

How to use Kotlin Higher Order Function in nested Recyler View

I have a recycler view(Parent) and inside it, I have another recycler View (Child).
There are 2 operations in child recycler View which I want to get on Fragment Class and do some things dynamically.
Architecture: MVVM
Yes, you can achieve your desired behavior by following these steps:
I will use Lambda to refer to Higher Order Function.
Pass the Lambda function from Activity/Fragment -> Parent Adapter
Pass the Lambda function from Parent Adapter -> Child Adapter.
For example, this code shows how to get a callback from nested Recyclerview when a user clicks Error Item from child Recyclerview.
//In Activity/Fragment
private var errorClick: () -> Unit
parentAdapter.setErrorClick(errorClick)
//In Parent Adapter
private var errorClick: () -> Unit
childAdapter.setErrorClick(errorClick)
//In Child Adapter - Now use errorClick to callback methods to Activity/Fragment
private var errorClick: () -> Unit // Use IT!
Let me know if you have any questions. Thanks.
Here is an example for higher order function.
TextAdapter.kt class
class TextAdapter(
val onClick: (String) -> Unit
): RecyclerView.Adapter<TextAdapter.ViewHolder>() {
inner class ViewHolder(val binding: ItemTextBinding) :
RecyclerView.ViewHolder(binding.root)
private val list: ArrayList<String> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
ViewHolder =
ViewHolder(
ItemTextBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val adapter = InnerAdapter {
onClick.invoke(it)
}
binding.recyclerViewList.adapter = adapter
binding.recyclerViewList.setData(list)
}
override fun getItemCount(): Int = list.size
fun setData(newList: ArrayList<String>) {
list.clear()
list.addAll(newList)
}
}
Inner adapter
class InnerAdapter(
val onClick: (String) -> Unit
): RecyclerView.Adapter<InnerAdapter.ViewHolder>() {
inner class ViewHolder(val binding: ItemText1Binding) :
RecyclerView.ViewHolder(binding.root)
private val list: ArrayList<String> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
ViewHolder =
ViewHolder(
ItemText1Binding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
binding.tvTxt.text = list[position]
holder.itemView.setOnClickListener {
//change background color
onClick.invoke(list[position])
}
}
override fun getItemCount(): Int = list.size
fun setData(newList: ArrayList<String>) {
list.clear()
list.addAll(newList)
}
}
Now you can get the value of the item click on the setAdapter
Let's see how
The below function is called from the fragment class where the adapter is set
val adapter = TextAdapter {
showToast(it)
}

Can i have multiple recyclerviews with same item layout?

I have differents screens that use recyclerview but they have a very similar item layout. The item layout is basically a textview and an image. The image is the same and never changes and the textview style is the same but the text changes because it comes from the api.
I started using the same item layout for each recyclerview because it was very similar but i don't know how to make it work and if it would take too much time
Below is the simplest way to change what to display. You can pass a boolean in the constructor of the adapter from the parent classes.
For example, If it is true, show some data. If it is false you can show some other data.
class RecyclerAdapter(var displayUserData: Boolean) : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {...}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = values[position]
if(displayUserData){
holder.textView.text = item.name
}else{
holder.textView .text = item.otherStuff
}
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {...}
}
Then set the adapter with true or false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val adapter = RecyclerAdapter(true)
}

Problem with Kotlin Generics in a ListAdapter

I am trying to right a Generic ListAdapter for RecyclerView. I have 3 things I want to pass into the Adapter. The List of items, the row layout to use and the ViewHolder. I am able to get the list generically and the layout, but its the ViewHolder. Here is what I have so far but I am still new to Generics in Kotlin. I tried using the Class out method and then I am having issues with calling the constructor for a specific viewholder.
abstract class AbstractListAdapter(
private val items: List<*>,
private val layoutId: Int,
private val viewHolderClass: ???? >
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
return viewHolderClass.??? // need to call the constructor of the specific viewholder passed in
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemViewType(position: Int): Int {
return position
}
override fun getItemCount(): Int {
return items.size
}
}
// List adapter uses abstract
class ListAdapter(private items: List<Files>, private val id: Int, private viewHolder: FileViewHolder) : AbstractListAdapter(items, id, fileViewHolder) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
... some code here for the viewholder and list combining
}
}
// Specific VH that want to pass into abstract generic yet it will call the constructor from there.
class FileViewHolder(fileView: View) {
.... grab views for specific layout
}
You maybe do this by taking a ViewHolder constructor instead of class. That way you won't have to use reflection to instantiate the ViewHolder. You need to have a generic type for the ViewHolder, so your subclasses can properly implement onBindViewHolder and have access to the specific type of ViewHolder.
Also, you must make the items property have a type, or you won't be able to use it. And your subclass probably needs to be able to access it, so it needs to be protected, not private.
I did not test this:
abstract class AbstractListAdapter<VH: RecyclerView.ViewHolder> (
protected val items: List<*>,
private val layoutId: Int,
private val viewHolderConstructor: (View) -> VH >
) : RecyclerView.Adapter<VH>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
return viewHolderConstructor(v)
}
//...
}
Then to implement it, you specify the ViewHolder constructor in the superconstructor call and you specify the type. Since the type is specified, you don't need a constructor parameter for it in the subclass.
class ListAdapter(private items: List<Files>, private val id: Int) : AbstractListAdapter<FileViewHolder>(items, id, ::FileViewHolder) {
override fun onBindViewHolder(holder: FileViewHolder, position: Int) {
//...
}
}
That said, I'm not sure what this actually achieves for you. It just seems like moving code around. You will still have to extract view references from the parent view. You just have to do it in the ViewHolder initialization instead of in onCreateViewHolder. And now you will have to be careful to pass in the right layout that matches up with the specific ViewHolder type. You may as well remove that parameter and do the layout in the ViewHolder constructor to avoid that issue. But now all you've done is move the onCreateViewHolder functionality into your ViewHolder's init block.
Also, your version of the abstract class is subverting expected results of the functions you've overridden. Why would every item in the list have a different type? Why would the item ID's be based on list position? This just messes up the functionality for editing the list data (rearranging, adding and removing will be broken).

RecyclerView adapter `onCreateViewHolder` & `onBindViewHolder` invoked only once

I have a RecyclerView.Adapter like this:
internal class MyAdapter : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
private val data: List<MyModel> = SeedData().seed()
override fun onCreateViewHolder(v: ViewGroup, viewType: Int): MyViewHolder {
val binding = MyListitemBinding.inflate(LayoutInflater.from(v.context), v, false)
return MyViewHolder(binding)
}
override fun getItemCount() = data.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(data[position])
}
}
However, only the first item from the data is getting displayed (i.e. onCreateViewHolder & onBindViewHolder invoked only one time). How can I make it display all items from the data properly?
The commented answers above are correct. My list item (views) were full height of screen:
This means that RecyclerView would only update the ViewHolder once you scroll to the next element. The solution is to modify the height of these items.

How to define ViewHolder's constructor when I defining recyclerView with kotlin

I want to define a recyclerView with kotlin for first time. But I have a problem in viewHodler's constructor.
I don't know how to define ViewHolder's Constructor. Do I have to define a secondary constructor?
My code is:
class MyAdapter: RecyclerView.Adapter<ViewHolder> {
var array = ArrayList<Note>()
// onCreateView
// getItemCount
//onBindViewHolder
}
class ViewHolder: RecyclerView.ViewHolder{}// my view has a textView that i want to initialise but i don't know where do it
Example by google: custom adapter
Example in stackoverflow: simple recyclerView
Here is a simple usage with textView (from the official android developers website):
class MyViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): MyAdapter.MyViewHolder {
// create a new view
val textView = LayoutInflater.from(parent.context)
.inflate(R.layout.my_text_view, parent, false) as TextView
// set the view's size, margins, paddings and layout parameters
...
return MyViewHolder(textView)
}
// Replace the contents of a view (invoked by the layout manager)
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
holder.textView.text = myDataset[position]
}
I highly suggest you to go through recyclerView-android-devs and understanding-recyclerview-medium.
Here's an example NotesViewHolder in Kotlin:
class NotesViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(note: Note) {
// Bind your note stuff here
}
}
class MyAdapter : RecyclerView.Adapter<NotesViewHolder>() {
var array = ArrayList<Note>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotesViewHolder {
// TODO: Create you ViewHolder
}
override fun getItemCount(): Int {
// TODO: Set item count here
}
override fun onBindViewHolder(holder: NotesViewHolder, position: Int) {
holder.bind(array[position])
}
}

Categories

Resources