How to implement HeaderItems in Recyclerview using Groupie in Android - android

I am trying to use Groupie to create a recyclerview with HeaderItems. I have Group of Data like this
class Group(
val id: String = generateId(),
val name: String? = null,
val entries: List<Entry>? = null
) : Item(), Parcelable {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.apply {
itemView.tvGroupName.text = name
}
}
override fun getLayout() = R.layout.group_single_item
constructor(source: Parcel) : this(
source.readString(),
source.readString(),
source.createTypedArrayList(Entry.CREATOR)
)
override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
writeString(id)
writeString(name)
writeTypedList(entries)
}
companion object {
private fun generateId(): String {
return UUID.randomUUID().toString()
}
#JvmField
val CREATOR: Parcelable.Creator<Group> = object : Parcelable.Creator<Group> {
override fun createFromParcel(source: Parcel): Group = Group(source)
override fun newArray(size: Int): Array<Group?> = arrayOfNulls(size)
}
}
}
Every group has a list of entries
data class Entry(val id: Long=0, val name: String) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readLong(),
parcel.readString()
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeLong(id)
parcel.writeString(name)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Entry> {
override fun createFromParcel(parcel: Parcel): Entry {
return Entry(parcel)
}
override fun newArray(size: Int): Array<Entry?> {
return arrayOfNulls(size)
}
}
}
So I am trying to show a list of Groups along with their respective Entries. So I will be showing a Group with its name and the list of entries. So I thought of using Groupie for this one.
This is what I have been trying
val linearLayoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
val groups = intent.getParcelableArrayListExtra<Group>("groups")
val groupAdapter = GroupAdapter<GroupieViewHolder>().apply {
val section = Section(Group())
section.setHeader(Group())
section.addAll(groups)
this.add(section)
}
recyclerViewGroups.apply {
layoutManager = linearLayoutManager
adapter = groupAdapter
}
But I am not quite sure, how to add the Group along with its Entries. Any help would be appreciated. Thanks

First you need to create item classes for your groups (possibly header and entry).
Follow instructions in this section.
E.g. those could be:
class HeaderItem(private val groupName: String) : Item() {
//... to be implemented
}
and
class EntryItem(private val entryName: String) : Item() {
//... to be implemented
}
and then use them in your adapter (needs to be tested, I'm writing this off the top of my head):
val groupAdapter = GroupAdapter<GroupieViewHolder>().apply {
groups.forEach { group ->
val section = Section()
section.setHeader(HeaderItem(group.name))
section.addAll(group.entries.map{ it -> EntryItem(it.name) })
this.add(section)
}
}

Related

Complex and nested firebase realtime database reading with firebase recycler adapter

The first data node that is immediately there under user id will contain a linked list type data. Each index can contain two parts: data and info. The confusion is due to the unique key -MkqHbtLzqzI... which is there immediately under index data. Each index data will have a list of such a different and dynamic key. Matrix is a List<Float>.
We want to use FirebaseRecyclerAdapter where I will be required to give the type of class under setQuery method. But I am confused how to make equivalent pojo class (or data class in kotlin) of such a structure.
Objects that we want to load in a recyclerView starts from 2. So, 6154... is a userId under which we have a node called data as shown in the given screenshot. This data contains a list of items in a linkedList<Long> fashion that we want to display in a recyclerView.
I have tried with below data classes but the problem is, it neither contains the index (2 in screenshot) nor the unique key (-MkqH...).
UserData (A class that I am using for firebaseRecyclerViewAdapter)
#Keep
#IgnoreExtraProperties
data class UserData(
var data: UserPrefsData? = null,
var info: UserPrefsInfo? = null
) {
#Exclude
fun toMap(): Map<String, Any?> {
return mapOf(
"data" to data,
"info" to info
)
}
}
UserPrefsData
#Keep
#IgnoreExtraProperties
data class UserPrefsData(
var color: String = BRUSH_BLACK,
var stroke: Float = 8f,
var data: String = "",
var shape: Shape? = null,
var fill: Boolean = false,
var matrix : ArrayList<Float>? = null
) {
#Exclude
fun toMap(): Map<String, Any?> {
return mapOf(
"color" to color,
"stroke" to stroke,
"data" to data,
"shape" to shape,
"fill" to fill,
"matrix" to matrix
)
}
}
UserPrefsInfo
#Keep
#IgnoreExtraProperties
data class PageInfo(
var color: String = BRUSH_WHITE,
var backGround: BackGround = BackGround.COLOR,
var orientation: Int = ORIENTATION_PORTRAIT,
var previous: Int = -1,
var next: Int = -1
) {
#Exclude
fun toMap(): Map<String, Any?> {
return mapOf(
"color" to color,
"backGround" to backGround,
"orientation" to orientation,
"previous" to previous,
"next" to next
)
}
}
Activity
private fun initFirebaseAdapter(): FirebaseAdapter? {
val userDataRef = databaseReference.child("data")
val options = FirebaseRecyclerOptions.Builder<UserData>()
.setLifecycleOwner(this)
.setQuery(userDataRef, UserData::class.java)
.build()
return FirebaseAdapter(
options,
isAdmin,
rvItemEventListener
)
}
private fun setRecyclerViewAdapter(
recyclerView: RecyclerView?,
recyclerViewAdapter: RecyclerView.Adapter<out RecyclerView.ViewHolder>?
) {
recyclerView?.adapter = recyclerViewAdapter
}
rvFirebaseAdapter = initFirebaseAdapter()
setRecyclerViewAdapter(binding.idVRv, rvFirebaseAdapter)
FirebaseAdapter
class FirebaseAdapter(var options: FirebaseRecyclerOptions<UserData>,
val showDeleteOption: Boolean,
private val callbackListener: Callbacks.RecyclerViewItemCallback):
FirebaseRecyclerAdapter<UserData, FirebaseAdapter.UserViewHolder>(options) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val itemView =
LayoutInflater.from(parent.context).inflate(R.layout.user_item, parent, false)
val binding = UserItemBinding.bind(itemView)
return UserViewHolder(binding, itemView)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :onBindViewHolder: position: $position item: ${getItem(position)} _getting data here - it is working_")
if (position != RecyclerView.NO_POSITION) {
bindData(holder, position)
}
}
private fun bindData(holder: UserViewHolder, position: Int) {
Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :bindData: position: $position" _This is also working_)
holder.binding.idVTvPageNumber.text = (position + 1).toString()
}
override fun getItemCount(): Int {
Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :superGetItemCount: ${super.getItemCount()}")
return super.getItemCount()
}
inner class UserViewHolder(val binding: UserItemBinding, itemView: View): RecyclerView.ViewHolder(itemView) {
init {
if (showDeleteOption) {
binding.idVIvDelete.setOnClickListener {
Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: UserViewHolder: :bindingAdapterPosition: $bindingAdapterPosition absoluteAdapterPosition: $absoluteAdapterPosition" _This is working_)
onDeleteItem(adapterPosition, binding.idVIvDelete)
}
} else {
binding.idVIvDelete.visibility = View.GONE
}
}
}
private fun onDeleteItem(adapterPosition: Int, idVIvDelete: View) {
if (adapterPosition != RecyclerView.NO_POSITION) {
Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :onDeleteItem: position: $adapterPosition" _This is also working_)
val item = getItem(adapterPosition)
getRef(adapterPosition).removeValue()
callbackListener.onDeleteItem(adapterPosition, item, idVIvDelete, itemCount)
notifyItemRemoved(adapterPosition)
}
}
override fun onBindViewHolder(
holder: UserViewHolder,
position: Int,
model: UserData
) {
Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :onBindViewHolder: firebase: position: $position UserData: $model" _This is never being called_)
if (position != RecyclerView.NO_POSITION) {
bindData(holder, position)
}
}
override fun onChildChanged(
type: ChangeEventType,
snapshot: DataSnapshot,
newIndex: Int,
oldIndex: Int
) {
super.onChildChanged(type, snapshot, newIndex, oldIndex)
Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :onChildChanged: snapshot: $snapshot" _This is working_)
}
override fun onDataChanged() {
super.onDataChanged()
Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :onDataChanged: ")
}
override fun onError(error: DatabaseError) {
super.onError(error)
Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :onError: $error")
}
override fun getItem(position: Int): UserData {
Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :getItem: position: $position")
return super.getItem(position)
}
override fun getRef(position: Int): DatabaseReference {
Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :getRef: position: $position")
return super.getRef(position)
}
}
Update: 06th Oct 2021
_The default onBindViewHolder is calling onBindViewHolder of FirebaseRecyclerAdapter and in our implementation, there was no super call. Hence, after removing onBindViewHolder(holder: UserViewHolder, position: Int) , onBindViewHolder(holder: UserViewHolder, position: Int, model: UserData) is getting called. But I still do not know how to get those indices and unique keys.
Thank you in anticipation. Any reference link, suggestion, guidance will also be helpful.
For such nested data, we can use a SnapshotParser. Also, in order to fulfil the requirements, I had to change data class as below:
UserData
#Keep
#IgnoreExtraProperties
data class UserData(
#Exclude
var dataIndex: Long? = null,
#Exclude
var userDataItemKey: String? = null,
#Exclude
var listOfUserPrefsData: MutableList<UserPrefsData?>? = null,
#JvmField
#PropertyName(AppFirebase.DATA)
var data: UserPrefsData? = null,
#JvmField
#PropertyName(AppFirebase.INFO)
var userPrefsInfo: UserPrefsInfo? = null
) {
#Exclude
fun toMap(): Map<String, Any?> {
return mapOf(
"data" to data,
"info" to userPrefsInfo
)
}
}
After that, I have used SnapshotParser as below:
private fun initFirebaseAdapter(): FirebaseAdapter? {
val userDataRef = databaseReference.child(AppFirebase.DATA)
val options = FirebaseRecyclerOptions.Builder<UserData>()
.setLifecycleOwner(this)
.setQuery(userDataRef) {
val userPrefsInfo: UserPrefsInfo?
var userDataItemKey: String? = null
val listOfUserPrefsData = mutableListOf<UserPrefsData?>()
val dataIndex = it.key?.toLong()
val userPrefsInfoRef = it.child(AppFirebase.INFO)
userPrefsInfo = userPrefsInfoRef.getValue<UserPrefsInfo>()
val userPrefsDataRef = it.child(AppFirebase.DATA)
for (userDataItem in userPrefsDataRef.children) {
userDataItemKey = userDataItem.key
val userPrefsData = userDataItem.getValue<UserPrefsData>()
userPrefsData?.userDataItemKey = userDataItemKey
listOfUserPrefsData.add(userPrefsData)
}
UserData(
dataIndex = dataIndex,
userDataItemKey = userDataItemKey,
listOfUserPrefsData = listOfUserPrefsData,
userPrefsInfo = userPrefsInfo
)
}
.build()
return FirebaseAdapter(
options,
isAdmin,
rvWhiteboardItemEventListener
)
}
Now I have that 2 indices, that list of -Mkq... unique keys including UserPrefsData and UserPrefsInfo for each index. So, basically, everything that is there under data of 6154... including indices and unique keys...

how to implement search viewmodel and show it in recyclerview in kotlin

I am developing tvshows app where I am implementing following logic user search tvshows and filtered result has to show in recyclerview but I want to implement filtering functionality in viewmodel
how can I achieve that
below interface class
interface ApiInterface {
#GET("search/shows")
suspend fun searchShows( #Query("q") query: String): Call<TvMazeResponse>
}
below TvRepository.kt
class TvRepository(private val apiInterface: ApiInterface) {
suspend fun getShows() = apiInterface.searchShows("")
}
below adapter class
class TvAdapter : RecyclerView.Adapter<TvAdapter.ViewHolder>(), Filterable {
lateinit var tvMazeList: MutableList<TvMazeResponse>
lateinit var filterResult: ArrayList<TvMazeResponse>
override fun getItemCount(): Int =
filterResult.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.tv_item, parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(filterResult[position])
}
fun addData(list: List<TvMazeResponse>) {
tvMazeList = list as MutableList<TvMazeResponse>
filterResult = tvMazeList as ArrayList<TvMazeResponse>
notifyDataSetChanged()
}
override fun getFilter(): Filter {
return object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val charString = constraint?.toString() ?: ""
if (charString.isEmpty()) filterResult =
tvMazeList as ArrayList<TvMazeResponse> else {
val filteredList = ArrayList<TvMazeResponse>()
tvMazeList
.filter {
(it.name.contains(constraint!!)) or
(it.language.contains(constraint))
}
.forEach { filteredList.add(it) }
filterResult = filteredList
}
return FilterResults().apply { values = filterResult }
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
filterResult = if (results?.values == null)
ArrayList()
else
results.values as ArrayList<TvMazeResponse>
notifyDataSetChanged()
}
}
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(result: TvMazeResponse) {
with(itemView) {
Picasso.get().load(result.image.medium).into(imageView)
}
}
}
}
below Constants.kt
object Constants {
const val BASE_URL = "https://api.tvmaze.com/"
}
below TvMazeResponse.kt
data class TvMazeResponse(
#SerializedName("averageRuntime")
val averageRuntime: Int,
#SerializedName("dvdCountry")
val dvdCountry: Any,
#SerializedName("externals")
val externals: Externals,
#SerializedName("genres")
val genres: List<String>,
#SerializedName("id")
val id: Int,
#SerializedName("image")
val image: Image,
#SerializedName("language")
val language: String,
#SerializedName("_links")
val links: Links,
#SerializedName("name")
val name: String,
#SerializedName("network")
val network: Network,
#SerializedName("officialSite")
val officialSite: String,
#SerializedName("premiered")
val premiered: String,
#SerializedName("rating")
val rating: Rating,
#SerializedName("runtime")
val runtime: Int,
#SerializedName("schedule")
val schedule: Schedule,
#SerializedName("status")
val status: String,
#SerializedName("summary")
val summary: String,
#SerializedName("type")
val type: String,
#SerializedName("updated")
val updated: Int,
#SerializedName("url")
val url: String,
#SerializedName("webChannel")
val webChannel: Any,
#SerializedName("weight")
val weight: Int
)
below TvViewModel.kt
class TvViewModel(apiInterface: ApiInterface) : ViewModel() {
}
I want to implement filter and search function in viewmodel how can I achieve that any help and tips greatly appreciated
In TvRepository change the getShows function to
suspend fun getShows(searchString:String) = apiInterface.searchShows(searchString)
Then in the ViewModel change the constructor to get an instance of the TVRepository and call API as shown below
class TvViewModel( tvRepository: TvRepository) : ViewModel() {
fun getShows(searchParameter:String){
viewModelScope.launch(Dispatchers.IO){
val response= tvRepository.getShows().awaitResponse()
if(response.isSuccessful{
//api success you can get result from response.body
}
else{
//api failed
}
}
}
}

Android Kotlin RecyclerView Search Item with EditText with data from Data Class

So I have a RecyclerView which data is populated with data from some Data Class. I want to Apply a search item function for this recyclerview. Back when I don't use Data Classes, I followed this tutorial (The tutorial is in Java).
Now that I'm using a data class since I fetch the data that populate the recyclerview from an API endpoint, I'm quite confused on how to apply the search function in the current recyclerview.
(I fetch the data using library Retrofit if you wondering.)
This is the code snippet from the fragment:
RefundListFragment.kt
private fun fetchRefundListData() {
NetworkConfig().getRefundListDetailService().getRefundList().enqueue(object :
Callback<RefundListPOJODataClass> {
override fun onFailure(call: Call<RefundListPOJODataClass>, t: Throwable) {
...
...
...
}
override fun onResponse(
call: Call<RefundListPOJODataClass>,
response: Response<RefundListPOJODataClass>
) {
binding.refundProgressBar.visibility = View.GONE
binding.rvRefundList.adapter =
response.body()?.let { RefundListAdapter(it, this#RefundListFragment) }
fun filterString(text: String) {
val filteredList: List<RefundListPOJODataClass> = ArrayList()
for (item in response.body()?.data!!) {
if (item != null) {
if (item.buyerName?.toLowerCase()?.contains(text.toLowerCase())!!) {
filteredList.add(item)
}
}
}
adapter.filterList(filteredList)
}
binding.refundListSearchBar.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
filterString(s.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
}
})
}
However it returned an error:
Unresolved reference: add
This is the recyclerview Adapter:
RefundListAdapter.kt
class RefundListItemHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val refundedOrderId: TextView = itemView.refundedOrderId
private val orderDateTime: TextView = itemView.orderDateTime
private val refundedCustomerName: TextView = itemView.refundedCustomerName
fun bind(
refundHistory: RefundListPOJODataClassDataItem,
clickListener: RefundListOnItemClickListener
) {
refundedOrderId.text = refundHistory.orderId
orderDateTime.text = refundHistory.orderDate
refundedCustomerName.text = refundHistory.buyerName
itemView.setOnClickListener {
clickListener.onItemClicked(refundHistory)
}
}
}
class RefundListAdapter(
private var refundList: RefundListPOJODataClass,
private val itemClickListener: RefundListOnItemClickListener
) : RecyclerView.Adapter<RefundListItemHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RefundListItemHolder {
val v = LayoutInflater.from(parent.context)
.inflate(R.layout.refund_list_item_layout, parent, false)
return RefundListItemHolder(v)
}
override fun getItemCount(): Int {
if (refundList.data != null) {
return refundList.data!!.size
} else {
return 0
}
}
override fun onBindViewHolder(holder: RefundListItemHolder, position: Int) {
val refundHistory = refundList.data?.get(position)
if (refundHistory != null) {
holder.bind(refundHistory, itemClickListener)
}
}
fun filterList(filteredList: List<RefundListPOJODataClass>) { //This is the function I called in the fragment
refundList = filteredList.get(0)
notifyDataSetChanged()
}
}
interface RefundListOnItemClickListener {
fun onItemClicked(refundHistory: RefundListPOJODataClassDataItem)
}
And this is how the data class looks like
RefundListPOJODataClass.kt
data class RefundListPOJODataClass(
#field:SerializedName("data")
val data: List<RefundListPOJODataClassDataItem?>? = null,
#field:SerializedName("error")
val error: Error? = null
)
data class RefundListPOJODataClassError(
#field:SerializedName("msg")
val msg: String? = null,
#field:SerializedName("code")
val code: Int? = null,
#field:SerializedName("status")
val status: Boolean? = null
)
data class RefundListPOJODataClassDataItem(
#field:SerializedName("order_date")
val orderDate: String? = null,
#field:SerializedName("buyer_name")
val buyerName: String? = null,
#field:SerializedName("phone_number")
val phoneNumber: String? = null,
#field:SerializedName("status_refund")
val statusRefund: String? = null,
#field:SerializedName("order_id")
val orderId: String? = null,
#field:SerializedName("refund")
val refund: Int? = null,
#field:SerializedName("refund_date")
val refundDate: String? = null
)
I want to search the recyclerview item by the attribute buyerName. How can I achieve it ? What should i change in my code? If there's any detail I missed to tell, Just let me know.
Your filtered list is of type List, which is immutable.
Change it to array list or MutableList:
val filteredList: ArrayList<RefundListPOJODataClass> = ArrayList()

Dynamic RecyclerView Adapter accepting any list

I have an app in which depending on the currency selected, I pass the list to the adapter and based on the type of list passed as an argument, I decide which model class should be used.
RecyclerView Adapter
class CoinAdapter : RecyclerView.Adapter<CoinAdapter.MyViewHolder> {
private var coinList: List<Coin>? = null
private var coinINRList: List<CoinINR>? = null
private var coinEURList: List<CoinEUR>? = null
private var coinGBPList: List<CoinGBP>? = null
private var context: Context? = null
inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var coinName: TextView
var coinPrice: TextView
init {
coinName = view.findViewById(R.id.coin_title_text)
coinPrice = view.findViewById(R.id.coin_price_text)
}
}
constructor(coinList: List<Coin>?, context: Context?) {
this.coinList = coinList
this.context = context
}
constructor(coinList: List<CoinINR>?, context: Context?, second: String) {
this.coinINRList = coinList
this.context = context
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.coin_list_row, parent, false)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
when (currencyUnit) {
"USD" -> {
val coin = coinList?.get(position)
holder.coinName.text = coin?.name
holder.coinPrice.text = coin?.price
}
"INR" -> {
val coinINR = coinINRList?.get(position)
holder.coinName.text = coinINR?.name
holder.coinPrice.text = coinINR?.price
}
}
}
override fun getItemCount(): Int {
when (currencyUnit) {
"USD" -> return coinList?.size ?: 0
"INR" -> return coinINRList?.size ?: 0
else -> return coinList?.size ?: 0
}
}
}
Now, I need to support multiple currencies and so the code is becoming boilerplate. Is there any way that I can make the RecyclerView accept any type of list and then perform task depending on the list?
Thanks in advance.
My suggestion is to create a class Coin that will be a parent of all other currency objects.
open class Coin(val name: String, val price: Float)
data class CoinINR(name: String, price: Float) : Coin(name, price)
Than your adapter would have only one List and your onBindViewHolder method will look like this:
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
with (coinList?.get(position)) {
holder.coinName.text = it.name
holder.coinPrice.text = it.price
}
}

Generic RecyclerView adapter

I want to have generic RecyclerView to be able to reuse it. In my case I have 2 models: CategoryImages and Category. While trying to add constructor() it brings the following errors. I know the second one is because it understands like both primary and secondary constructor are same.
Is it possible to do such kind of thing? If yes, then how? if no - thank you.
Here is CategoryImage:
class CategoryImage {
#SerializedName("url")
private var url: String? = null
fun getUrl(): String? {
return url
}
}
And here is Category:
class Category {
#SerializedName("_id")
var id: String? = null
#SerializedName("name")
var name: String? = null
#SerializedName("__v")
var v: Int? = null
#SerializedName("thumbnail")
var thumbnail: String? = null
}
Here is the part of RecyclerViewAdapter's constructor:
class RecyclerViewAdapter(var arrayList: ArrayList<CategoryImage>?, var fragment: Int): RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>() {
constructor(arrayList: ArrayList<Category>, fragment: Int): this(arrayList, fragment)
}
I want to have generic RecyclerView to be able to reuse it.
That's nice intention, then why you haven't made your adapter generic?
I think you can adopt the approach outlined by Arman Chatikyan in this blog post. After applying some Kotlin magic you'll only need following lines of code in order to setup your RecyclerView:
recyclerView.setUp(users, R.layout.item_layout, {
nameText.text = it.name
surNameText.text = it.surname
})
And if you need to handle clicks on RecyclerView items:
recyclerView.setUp(users, R.layout.item_layout, {
nameText.text = it.name
surNameText.text = it.surname
}, {
toast("Clicked $name")
})
Now the adapter of the RecyclerView is generic and you are able to pass list of any models inside setup() method's first argument.
In this section I will copy-paste sources from the blog post, in order to be evade from external sources deprecation.
fun <ITEM> RecyclerView.setUp(items: List<ITEM>,
layoutResId: Int,
bindHolder: View.(ITEM) -> Unit,
itemClick: ITEM.() -> Unit = {},
manager: RecyclerView.LayoutManager = LinearLayoutManager(this.context)): Kadapter<ITEM> {
return Kadapter(items, layoutResId, {
bindHolder(it)
}, {
itemClick()
}).apply {
layoutManager = manager
adapter = this
}
}
class Kadapter<ITEM>(items: List<ITEM>,
layoutResId: Int,
private val bindHolder: View.(ITEM) -> Unit)
: AbstractAdapter<ITEM>(items, layoutResId) {
private var itemClick: ITEM.() -> Unit = {}
constructor(items: List<ITEM>,
layoutResId: Int,
bindHolder: View.(ITEM) -> Unit,
itemClick: ITEM.() -> Unit = {}) : this(items, layoutResId, bindHolder) {
this.itemClick = itemClick
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.itemView.bindHolder(itemList[position])
}
override fun onItemClick(itemView: View, position: Int) {
itemList[position].itemClick()
}
}
abstract class AbstractAdapter<ITEM> constructor(
protected var itemList: List<ITEM>,
private val layoutResId: Int)
: RecyclerView.Adapter<AbstractAdapter.Holder>() {
override fun getItemCount() = itemList.size
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): Holder {
val view = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false)
return Holder(view)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val item = itemList[position]
holder.itemView.bind(item)
}
protected abstract fun onItemClick(itemView: View, position: Int)
protected open fun View.bind(item: ITEM) {
}
class Holder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
Assuming CategoryImage means a Category with image.
You can express this relationship with inheritance:
open class Category(
val name: String
)
class CategoryImage(
name: String,
val image: String
) : Category(name)
class RecyclerViewAdapter(
val arr: List<Category>,
val fragment: Int
) {
fun bind(i: Int) {
val item = arr[i]
val name: String = item.name
val image: String? = (item as? CategoryImage)?.image
}
}
Another options it to have a common interface (which removes that ugly cast):
interface CategoryLike {
val name: String
val image: String?
}
class Category(
override val name: String
) : CategoryLike {
override val image: String? = null
}
class CategoryImage(
override val name: String,
override val image: String
) : CategoryLike
class RecyclerViewAdapter(private var arr: List<CategoryLike>, var fragment: Int) {
fun bind(i: Int) {
val item = arr[i]
val name: String = item.name
val image: String? = item.image
}
}
In both cases the following works (just to see that it can be compiled):
fun testCreation() {
val cats: List<Category> = listOf()
val catImages: List<CategoryImage> = listOf()
RecyclerViewAdapter(cats, 0)
RecyclerViewAdapter(catImages, 0)
}
Tip: don't use ArrayList, List (listOf(...)) or MutableList (mutableListOf(...)) should be enough for all your needs.
Tip: try to use val as much as you can, it helps prevent mistakes.
Wish: Next time please also include some relevant parts of your code in a copy-able form (not screenshot), so we don't have to re-type it and have more context. See https://stackoverflow.com/help/mcve
One "terrible" way of doing it is to simply have 1 constructor taking an ArrayList of Objects and perform an instanceof on the objects.
Both methods have the same signature, because type parameters are not considered as different types (for Java Virtual Machine both are just ArrayLists). You also need to be aware of type erasure.
Check this repository https://github.com/shashank1800/RecyclerGenericAdapter
lateinit var adapter: RecyclerGenericAdapter<AdapterItemBinding, TestModel>
...
val clickListener = ArrayList<CallBackModel<AdapterItemBinding, TestModel>>()
clickListener.add(CallBackModel(R.id.show) { model, position, binding ->
Toast.makeText(context, "Show button clicked at $position", Toast.LENGTH_SHORT)
.show()
})
adapter = RecyclerGenericAdapter(
R.layout.adapter_item, // layout for adapter
BR.testModel, // model variable name which is in xml
clickListener // adding click listeners is optional
)
binding.recyclerView.adapter = adapter
binding.recyclerView.layoutManager = LinearLayoutManager(this)
adapter.submitList(viewModel.testModelList)
Recycler adapter item R.layout.adapter_item XML.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="testModel"
type="com.packagename.model.TestModel" />
</data>
...
VERY IMPORTANT NOTE: I'm using same layout for all my screens.
//********Adapter*********
// include a template parameter T which allows Any datatype
class MainAdapter<T : Any>(var data: List<T>) : RecyclerView.Adapter<MainViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
val view = parent.inflateLayout()
return MainViewHolder(view)
}
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
val item = data[position]
holder.bind(item)
}
override fun getItemCount(): Int = data.size
class MainViewHolder(private val binding: MainItemsListBinding) :
RecyclerView.ViewHolder(binding.root) {
// do the same for for bind function on Viewholder
fun <T : Any> bind(item: T) {
// Extension function see code below
binding.appInfo.mySpannedString(item)
}
}
}
//Cast Item to type
fun <T : Any> TextView.mySpannedString(item: T) {
when (item.javaClass.simpleName) {
"AaProgram" -> {
item as AaProgram
this.text = buildSpannedString {
appInfo(item.numero, item.principio)
}
}
"AppContent" -> {
item as AppContent
this.text = buildSpannedString {
appInfo(item.title, item.coment, item.footnote)
}
}
"AutoDiagnostic" -> {
item as AppContent
this.text = buildSpannedString {
appInfo(item.title, item.coment, item.footnote)
}
}
"GroupDirectory" -> {}
"ReflexionsBook" -> {}
"County" -> {}
"States" -> {}
"Towns" -> {}
}
}

Categories

Resources