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...
Related
I have a RecyclerView where an item can be edited via a DialogFragment, so when an item is clicked a Dialog is shown, then I can change some properties of that item, the issue is that RecyclerView is not updated with the updated properties and I have to force a notifyItemChanged when the Dialog is closed.
When an item in RecyclerView is clicked I set a MutableLiveData in my ViewModel so then it can be manipulated in the Dialog.
My ViewModel looks like this:
#HiltViewModel
class DocumentProductsViewModel #Inject constructor(private val repository: DocumentProductsRepository) :
ViewModel() {
val barcode = MutableLiveData<String>()
private val _selectedProduct = MutableLiveData<DocumentProduct>()
val selectedProduct: LiveData<DocumentProduct> = _selectedProduct
private val _selectedDocumentId = MutableLiveData<Long>()
val selectedDocumentId: LiveData<Long> = _selectedDocumentId
val products: LiveData<List<DocumentProduct>> = _selectedDocumentId.switchMap { documentId ->
repository.getDocumentProducts(documentId).asLiveData()
}
fun insert(documentProduct: DocumentProduct) = viewModelScope.launch {
repository.insert(documentProduct)
}
fun setProductQuantity(quantity: Float) {
_selectedProduct.value = _selectedProduct.value.also {
it?.timestamp = System.currentTimeMillis()
it?.quantity = quantity
}
update()
}
fun start(documentId: Long?) = viewModelScope.launch{
if (documentId == null) {
_selectedDocumentId.value = repository.getHeaderByType("Etichette")?.id
}
documentId?.let { documentId ->
_selectedDocumentId.value = documentId
}
}
fun select(product: DocumentProduct) {
_selectedProduct.value = product
}
fun delete() = viewModelScope.launch {
_selectedProduct.value?.let { repository.delete(it) }
}
private fun update() = viewModelScope.launch {
_selectedProduct.value?.let { repository.update(it) }
}
}
And in my fragment I'm subscribed to products as this:
private fun initRecyclerView() {
binding.rvProducts.adapter = adapter
viewModel.products.observe(viewLifecycleOwner) { products ->
val productsCount = products.count()
binding.tvProductsCount.text =
resources.getQuantityString(R.plurals.articoli, productsCount, productsCount)
// TODO: create amount string and set it with resources
binding.tvProductsAmount.text = productsCount.toEuro()
adapter.submitList(products)
binding.rvProducts.smoothScrollToPosition(adapter.itemCount - 1)
}
initSwipe(adapter)
}
When setProductQuantity is called the RecyclerView remains unchanged until notify is called while delete works fine without the necessity of calling any notify on RecyclerView.
UPDATE:
The item position is actually changed in RecyclerView as it's sorted by it's last changed timestamp BUT not the quantity field.
Here is my Adapter:
class DocumentProductsListAdapter : ListAdapter<DocumentProduct, DocumentProductsListAdapter.ViewHolder>(ProductDiffCallback) {
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
val view: View = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.layout_item, viewGroup, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val product = getItem(position)
holder.bind(product)
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val barcode: TextView = itemView.findViewById(R.id.barcode)
val quantity: TextView = itemView.findViewById(R.id.quantity)
val description: TextView = itemView.findViewById(R.id.description)
val unitOfMeasure: TextView = itemView.findViewById(R.id.unitOfMeasure)
fun bind(product: DocumentProduct) {
barcode.text = product.barcode
quantity.text = product.quantity.formatForQta().replace(".", ",")
if (product.labelType != null && product.labelType != "") {
unitOfMeasure.text = product.labelType
} else {
unitOfMeasure.text = product.unitOfMeasure?.lowercase(Locale.ITALIAN)
}
description.text = product.description ?: "-"
}
}
}
object ProductDiffCallback : DiffUtil.ItemCallback<DocumentProduct>() {
override fun areItemsTheSame(oldItem: DocumentProduct, newItem: DocumentProduct): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: DocumentProduct, newItem: DocumentProduct): Boolean {
return oldItem == newItem
}
}
data class DocumentProduct(
#PrimaryKey(autoGenerate = true)
var id: Long,
var barcode: String,
#Json(name = "desc")
var description: String?,
#ColumnInfo(defaultValue = "PZ")
#Json(name = "um")
var unitOfMeasure: String?,
#Json(name = "qta")
var quantity: Float,
#Json(name = "id_testata")
var documentId: Long,
#Json(name = "tipo_frontalino")
var labelType: String?,
var timestamp: Long?
) {
constructor(barcode: String, documentId: Long, labelType: String?) : this(
0,
barcode,
null,
"PZ",
1f,
documentId,
labelType,
null
)
override fun equals(other: Any?): Boolean {
return super.equals(other)
}
override fun hashCode(): Int {
return super.hashCode()
}
}
You have the implementations of areContentsTheSame() and areItemsTheSame() swapped.
areContentsTheSame() is asking if everything in the two items being compared is the same. Therefore, if the class has a proper equals()/hashcode() for all properties used by the ViewHolder, you can use oldItem == newItem. If you use a data class with all relevant properties in the primary constructor, then you don't need to manually override equals()/hashcode().
areItemsTheSame() is asking if the two items represent the same conceptual row item, with possible differences in their details. So it should be oldItem.id == newItem.id.
The problem with your data class is that you are overriding equals()/hashcode() without providing any implementation at all. This is effectively disabling the proper implementations that are provided by the data modifier by calling through to the super implementation in the Any class. You should not override them at all when you use data class.
Hello Im working on project using Kotlin (for the first time). My problem is i don't know how to get data "fullname" inside "data" list where one of the data inside the list also getting use for the condition, the name is "Role" to my RecyclerView in my Activity.
So i need show list "fullname" inside my RecyclerView where the "Role" is "Patient".
Here is the DataFileRecord:
data class DataFileRecord(
#field:SerializedName("total")
val total: Int? = null,
#field:SerializedName("data")
val data: List<DataItem>? = null,
#field:SerializedName("offset")
val offset: Int? = null,
#field:SerializedName("limit")
val limit: Int? = null,
#field:SerializedName("status")
val status: Int? = null
)
//get fullname
data class DataItem(
#field:SerializedName("updateDate")
val updateDate: String? = null,
#field:SerializedName("departement")
val departement: Departement? = null,
#field:SerializedName("isBlock")
val isBlock: String? = null,
#field:SerializedName("role")
val role: Role? = null,
#field:SerializedName("fullname")
val fullname: String? = null,
#field:SerializedName("id")
val id: String? = null,
#field:SerializedName("username")
val username: String? = null
)
//get role = patient
data class Role(
#field:SerializedName("name")
val name: String? = null,
#field:SerializedName("id")
val id: String? = null
)
data class Departement(
#field:SerializedName("name")
val name: String? = null,
#field:SerializedName("id")
val id: String? = null
)
Here is my Adapter:
class PatientAdapter(val context: List<DataItem>) : RecyclerView.Adapter<PatientAdapter.MyViewHolder>() {
var patientList: List<DataItem> = listOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_patient,parent,false)
return MyViewHolder(view)
}
override fun getItemCount(): Int {
return patientList.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int){
holder.patientName.text = patientList.get(position).fullname
}
#SuppressLint("NotifyDataSetChanged")
fun setPatientListItems(patientList: List<DataItem>){
this.patientList=patientList
notifyDataSetChanged()
}
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val patientName: TextView = itemView.findViewById(R.id.itemPatientName)
}
}
And here is where i stuck with my Call api in my activity:
//this 4 lines are inside oncreate
patientRecyclerView.setHasFixedSize(true)
linearLayoutManager = LinearLayoutManager(this)
patientRecyclerView.layoutManager = linearLayoutManager
patientRecyclerView.adapter = recyclerAdapter
fun getPatient(apiKey: String, apiSecret: String, token: String){
val retrofit = RetrofitClient.getClient()
retrofit.addGetPatient(apiKey,apiSecret,token)
.enqueue(object : Callback<DataFileRecord>{
override fun onResponse(call: Call<DataFileRecord>, response: Response<DataFileRecord>) {
TODO("Not yet implemented")
val dataPatient = response.body()
if (response.isSuccessful){
---------------- HERE WHERE I STUCK --------------------
}
}
override fun onFailure(call: Call<DataFileRecord>, t: Throwable) {
TODO("Not yet implemented")
}
})
}
Fragment into set list in adapter Code below :
binding.gvFolder.layoutManager = layoutManager
binding.gvFolder.adapter = AdapterGalleryPhotosFolder(this#GalleryImageFolderActivity,al_images,activityAddress)
below my Model class code:
data class ModelGalleryImagesFolder(val name:String, var al_imagepath: ArrayList<String>)
and below my full adapter code:
class AdapterGalleryPhotosFolder(
var context: Context,
var arrayList: ArrayList<ModelGalleryImagesFolder>,
var activityAddress: String?
):RecyclerView.Adapter<RecyclerView.ViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return MyViewHodler(AdapterPhotosfolderBinding.inflate(LayoutInflater.from(context),parent,false))
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as MyViewHodler)
val modelImages = arrayList[position]
holder.binding.tvFolder.text = modelImages.name
holder.binding.tvFolder2.text = arrayList[position].al_imagepath.size.toString()+" photo"
Glide.with(context).load(arrayList[position].al_imagepath[0])
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.into(holder.binding.ivImage)
holder.binding.secondlayout.setOnClickListener {
val intent = Intent(context, GalleryImageFolderIntoImagesActivity::class.java)
intent.putExtra("position", position)
intent.putExtra("value", activityAddress)
context.startActivity(intent)
(context as Activity).finish()
}
}
override fun getItemCount(): Int {
return arrayList.size
}
class MyViewHodler(var binding: AdapterPhotosfolderBinding) : RecyclerView.ViewHolder(binding.root)
}
Got the answer.. just put this inside response= isSuccessfull
namePatient?.data?.filter { it.role?.name=="Pasien" }.let { if (it != null) { dataP.addAll(it) } }
reciAdapter.notifyDataSetChanged()
So "dataP" is my lateinit var MutableList, and "reciAdapter" is my Recycler Adapter 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()
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)
}
}
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" -> {}
}
}