I am developing an Android application in Kotlin and I implemented a RecyclerView.
Each item of this RecyclerView contains one of the 3 combinations below:
a TextView + a TextView
a TextView + a Button
a TextView + a Spinner
In this activity, I have initialized my parameter list and I can add parameters of one of the 3 types mentioned above by clicking on the TEST button.
My problem is a UI problem: when there are a lot of parameters containing Spinners such that you have to scroll to see them, an arrow of a spinner is displayed at the very top of the RecyckerView (or at the very bottom sometimes). This spinner arrow starts flashing when I scroll and finally stops after a few seconds. Please note that I can't click on this arrow. I don't understand my mistake, can you help me?
Here is a link to a Youtube video recording of the behavior I have described that will help you better understand it.
Here is my view when I am on top of my RecyclerView:
This is my view when I scrolled down in the RecyclerView and there are parameters containing Spinners that are only visible when scrolling up again.
Here is my CustomAdapter class:
class CustomAdapter(private var parameterList: List<Parameter>) :
RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
sealed class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
class TextViewHolder(itemView: View) : ViewHolder(itemView) {
val textViewName: TextView = itemView.findViewById(R.id.parameterName)
val textViewValue: TextView = itemView.findViewById(R.id.parameterValue)
}
class ButtonViewHolder(itemView: View) : ViewHolder(itemView) {
var textViewName: TextView = itemView.findViewById(R.id.parameterName)
val buttonViewValue: Button = itemView.findViewById(R.id.parameterButton)
}
class SpinnerViewHolder(itemView: View) : ViewHolder(itemView) {
val textViewName: TextView = itemView.findViewById(R.id.parameterName)
val spinnerViewValue: Spinner = itemView.findViewById(R.id.parameterSpinner)
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
return when (viewType) {
0 -> {
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.parameter_text, viewGroup, false)
ViewHolder.TextViewHolder(view)
}
1 -> {
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.parameter_button, viewGroup, false)
ViewHolder.ButtonViewHolder(view)
}
2 -> {
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.parameter_spinner, viewGroup, false)
ViewHolder.SpinnerViewHolder(view)
}
else -> throw IllegalArgumentException("Invalid view type")
}
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
when (viewHolder) {
is ViewHolder.TextViewHolder -> {
viewHolder.textViewName.text = parameterList[position].parameterName
viewHolder.textViewValue.text = parameterList[position].parameterValue as CharSequence?
}
is ViewHolder.ButtonViewHolder -> {
viewHolder.textViewName.text = parameterList[position].parameterName
viewHolder.buttonViewValue.text = parameterList[position].parameterValue as CharSequence?
}
is ViewHolder.SpinnerViewHolder -> {
viewHolder.textViewName.text = parameterList[position].parameterName
viewHolder.spinnerViewValue.adapter = parameterList[position].parameterValue as SpinnerAdapter?
}
}
}
override fun getItemViewType(position: Int) : Int {
return parameterList[position].parameterType
}
override fun getItemCount() = parameterList.size
fun addNewList(newList: List<Parameter>){
parameterList = newList;
notifyDataSetChanged();
}
}
Each of the different types of parameters inherited from the class Parameter:
open class Parameter(open var parameterName: String? = "", open var parameterType: Int = 0, open var parameterValue: Any) {
}
ParameterText class:
class ParameterText(override var parameterName: String?, override var parameterValue: Any = "") : Parameter(parameterName, parameterValue = parameterValue!!) {
override var parameterType: Int = 0
}
ParameterButton class:
class ParameterButton(override var parameterName: String?, override var parameterValue: Any = "") : Parameter(parameterName, parameterValue = parameterValue!!) {
override var parameterType: Int = 1
}
ParameterSpinner class:
class ParameterSpinner(override var parameterName: String?, override var parameterValue: Any) : Parameter(parameterName, parameterValue = parameterValue) {
override var parameterType: Int = 2
}
Here is my NFCActivity:
class NFCActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_nfc)
buttonTest.setOnClickListener() {
parametersList = parametersList + ParameterSpinner(
"Led", ArrayAdapter(
this,
android.R.layout.simple_spinner_item, resources.getStringArray(R.array.LedState)
)
)
(rv_parameters2.adapter as CustomAdapter).addNewList(parametersList)
}
rv_parameters2.layoutManager = LinearLayoutManager(this)
rv_parameters2.adapter = CustomAdapter(parametersList)
}
private var parametersList : List<Parameter> = listOf<Parameter> (
ParameterText("Temperature", "24°C"),
ParameterText("Temperature", "24°C")
)
companion object {
fun getStartIntent(context: Context): Intent {
return Intent(context, NFCActivity::class.java)
}
}
}
NFCActivity's layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.ble.NFCActivity">
<Button
android:id="#+id/buttonTest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TEST"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_parameters2"
tools:listitem="#layout/parameter_text"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="#id/buttonTest"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Related
Trying to get a button to open another activity in android studio using kotlin. I am user a recycles view to do so. I have view card and want to edit the data in the card so I am user onClick to get the page to respond but that does seem to work I tried researching it but nothing come up that I can make sense of. Here is the code.
Thank you for all your help I appreciate it
This is the adapter class:
```class EventAdapter(var mListener: onItemClickListener, var context: Context, val items: ArrayList<EventModelk>):
RecyclerView.Adapter<EventAdapter.ViewHolder>() {
interface onItemClickListener {
fun onItemClick(position: Int)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemview = LayoutInflater.from(context).inflate(
R.layout.eventitems,
parent,
false,
)
return ViewHolder(itemview, mListener)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
holder.setEmail.text = item.eEmail
holder.setNotes.text = item.nNotes
holder.setData.text = item.dDate
holder.setTime.text = item.tTime
if (item.repeat == 1) {
holder.setReap.isChecked = true
// holder.setReap.text = item.repeat.toString()
}
if (position % 2 == 0) {
holder.main.setBackgroundColor(
ContextCompat.getColor(
context,
R.color.colorLightGray
)
)
} else {
holder.main.setBackgroundColor(ContextCompat.getColor(context, R.color.colorWhite))
}
holder.pushimg.setOnClickListener {
if (context is UpdateDataInfo) {
(context as UpdateDataInfo).updateEvent(item)
}
}
// interface onItemClickListener{
// fun onItemClick(position: Int)
}
override fun getItemCount(): Int {
return items.size
}
class ViewHolder(view: View,onItemClickListener: onItemClickListener): RecyclerView.ViewHolder(view),onItemClickListener {
val main: CardView = view.IIMain
val setData: TextView = view.set_date
val setTime: TextView = view.set_time
val setEmail: TextView = view.Tv_email
val setNotes: TextView = view.set_note
val setReap: CheckBox =view.is_repeat
val pushimg: ImageView = view.img_edit
val onItemClickListener = onItemClickListener
override fun onItemClick(position: Int) {
onItemClickListener.onItemClick(absoluteAdapterPosition)
}
}
}```
This is my code for getting the click to go to another activity:
class CurrentSch : AppCompatActivity(), EventAdapter.onItemClickListener {
//private var change =imageView2?.isClickable
var STORAGE_LOCAL =true
val eventlist = ArrayList<EventModelk>()
#RequiresApi(Build.VERSION_CODES.N)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.currentsch)
btnNewApp2.setOnClickListener {
val intent = Intent(this, NewPlan::class.java)
startActivity(intent)
}
seeAllEventInPlan()
}
//private var change =imageView2?.isClickable
private fun getEventist(): ArrayList<EventModelk>{
val eventdatabase = EventDatabasek(this)
return eventdatabase.veiwEvent()
}
fun seeAllEventInPlan() = if(getEventist().size >0){
ryc_eventLists.visibility = View.VISIBLE
viewnotview.visibility = View.GONE
ryc_eventLists.layoutManager = LinearLayoutManager(this)
val eventAdapter = EventAdapter(this,this, getEventist())
ryc_eventLists.adapter = eventAdapter
}else{
ryc_eventLists.visibility = View.GONE
viewnotview.visibility = View.VISIBLE
}
override fun onItemClick(position: Int) {
val item = eventlist[position]
img_edit.setOnClickListener {
val intent = Intent(this, UpdateDataInfo::class.java)
intent.putExtra("Events", position)
startActivity(intent)
}
}
}
Inside the onBindViewHolder function, simply call mListener.onItemClick(position) for example if you wanted to open another Activity on click of setData, then you can write below code in onBindViewHolder
holder.setData.setOnClickListener { mListener.onItemClick(position) }
according to your above code , you have made callbacks where you calling thee adapter you will get callback there and you will open a activity there .. .. but you can simple open your new activity from adapter like this ..
holder.itemView.setOnClickListener(View.OnClickListener {
val intent = Intent (context,SomeActivity::class.java)
context.startactivity(intent)
})
It is a note taking app. I am using Cloud Firestore to store data. Data got added into database but recycler view is not showing anything. Below are the code snippets.
My Data Model Class:
class Notes {
var id:String?= null
var title: String? = null
var description: String? = null
var created: Timestamp? =null
constructor() {}
constructor(id:String,title: String?, description:String?,created:Timestamp?) {
this.id=id
this.title = title
this.description=description
this.created=created
}
}
AddNotesActivity
class AddNoteActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_note)
btn_add_note.setOnClickListener{
val noteTitle = note_title.text.toString()
val noteDescription =note_description.text.toString()
if(noteTitle.isNotEmpty() && noteDescription.isNotEmpty()){
addNotes(noteTitle,noteDescription)
Toast.makeText(this,"note added successfully",Toast.LENGTH_SHORT).show()
onBackPressed()
}
}
}
private fun addNotes(title:String, description:String){
val currentUserId = FirebaseAuth.getInstance().currentUser!!.uid
var note = Notes(currentUserId,title,description, Timestamp(Date()))
FirebaseFirestore.getInstance().collection("notes").add(note).addOnSuccessListener {
Log.i("AddNoteActivity","Note added")
}
}
}
NotesActivity(showing recyclerview):
class NotesActivity : AppCompatActivity() {
lateinit var notesAdapter: NotesAdapter
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_notes)
recyclerView = findViewById(R.id.rv_notes)
setUpRecyclerView()
floating_btn.setOnClickListener {
startActivity(Intent(this, AddNoteActivity::class.java))
}
}
private fun setUpRecyclerView() {
val query:Query= FirebaseFirestore.getInstance().collection("notes").whereEqualTo("id",FirebaseAuth.getInstance().currentUser!!.uid)
val options:FirestoreRecyclerOptions<Notes> = FirestoreRecyclerOptions.Builder<Notes>().setQuery(query,Notes::class.java).build()
notesAdapter = NotesAdapter(options)
recyclerView.adapter = notesAdapter
notesAdapter!!.startListening()
}
}
Adapter class:
class NotesAdapter(options:FirestoreRecyclerOptions<Notes>):FirestoreRecyclerAdapter<Notes,NotesAdapter.MyViewHolder>(options) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotesAdapter.MyViewHolder {
val itemView= LayoutInflater.from(parent.context).inflate(R.layout.each_note_view,parent,false)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, p1: Int, NotesModel: Notes) {
holder.title.text = NotesModel.title
holder.description.text= NotesModel.description
val date=DateFormat.getDateInstance(DateFormat.MEDIUM).format(NotesModel.created)
holder.date.text = date.toString()
}
class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val title : TextView = itemView.title_text
val description: TextView =itemView.description_text
val date : TextView =itemView.date_created
}
}
But when getting data from querysnapshot, logcat is showing perfect data from firestore database:
val query = FirebaseFirestore.getInstance().collection("notes").whereEqualTo("id", FirebaseAuth.getInstance().currentUser!!.uid).get().addOnSuccessListener {
val doc = it.documents
for (i in doc) {
Log.i("NotesActivity", i.data.toString())
}
}
Logcat: {created=Timestamp(seconds=1611116973, nanoseconds=14000000),
description=day, id=wLxCTMLGZpaWNs1b8Uhf3HoRUgz2, title=go}
I spend two days on this, but not getting any solution. I would be thankful if anybody can solve the issue.
Below is XML file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_notes"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="7"
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/floating_btn"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_marginEnd="8dp"
android:backgroundTint="#color/light_blue"
android:src="#drawable/floating_btn"/>
I just want to say sorry to my English skill
I've studied the Android Studio and Kotlin these days.
but I'd got a problem on RecyclerViewer and Adapter, for Intent method
work flow chart
this image, this is what i want to do
so i coded the three classes
ShoppingAppActivity.kt, MyAdapter.kt, CartActivity.kt
At ShoppingAppActivity, If I click the itemId ( in the Red box texts)
I make it move to other context(CartActivity)
ShoppingAppActivity working
if i clicked the red box then
cartStatus
go to cart Activity
it worked but already I said, I just want to send only send itemID
covert to String (i will use toString())
SO i tried to use Intent method in ShoppingAppActivity.kt
///PROBLEM PART
adapter?.setOnItemClickListener{
val nextIntent = Intent(this, CartActivity::class.java)
//nextIntent.putExtra("itemID", )
startActivity(nextIntent)
}
///PROBLEM PART
like this but the problem is I don't know what am i have to put the parameter in
nextIntent.putExtra("itemID", )
what should i do?
I think, I should fix MyAdaptor.kt or ShopingAppActivity.kt for this problem.
But in my knowledge, this is my limit. :-(
below
Full Codes of ShoppingAppActivity.kt, MyAdapter.kt, CartActivity.kt
ShoppingAppActivity.kt
class ShoppingAppActivity : AppCompatActivity() {
lateinit var binding: ActivityShoppingAppBinding
private var adapter: MyAdapter? = null
private val db : FirebaseFirestore = Firebase.firestore
private val itemsCollectionRef = db.collection("items")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityShoppingAppBinding.inflate(layoutInflater)
setContentView(binding.root)
updateList()
binding.recyclerViewItems.layoutManager = LinearLayoutManager(this)
adapter = MyAdapter(this, emptyList())
binding.recyclerViewItems.adapter = adapter
///PROBLEM PART
adapter?.setOnItemClickListener{
val nextIntent = Intent(this, CartActivity::class.java)
//nextIntent.putExtra("itemID", )
startActivity(nextIntent)
}
///PROBLEM PART
}
private fun updateList() {
itemsCollectionRef.get().addOnSuccessListener {
val items = mutableListOf<Item>()
for (doc in it) {
items.add(Item(doc))
}
adapter?.updateList(items)
}
}
}
MyAdapter.kt
data class Item(val id: String, val name: String, val price: Int, val cart: Boolean) {
constructor(doc: QueryDocumentSnapshot) :
this(doc.id, doc["name"].toString(), doc["price"].toString().toIntOrNull() ?: 0, doc["cart"].toString().toBoolean() ?: false)
constructor(key: String, map: Map<*, *>) :
this(key, map["name"].toString(), map["price"].toString().toIntOrNull() ?: 0, map["cart"].toString().toBoolean() ?: false)
}
class MyViewHolder(val binding: ItemBinding) : RecyclerView.ViewHolder(binding.root)
class MyAdapter(private val context: Context, private var items: List<Item>)
: RecyclerView.Adapter<MyViewHolder>() {
fun interface OnItemClickListener {
fun onItemClick(student_id: String)
}
private var itemClickListener: OnItemClickListener? = null
fun setOnItemClickListener(listener: OnItemClickListener) {
itemClickListener = listener
}
fun updateList(newList: List<Item>) {
items = newList
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding: ItemBinding = ItemBinding.inflate(inflater, parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = items[position]
val itemID : String
holder.binding.textID.text = item.id
holder.binding.textName.text = item.name
if(item.cart)
{
holder.binding.textCart.text = "in Cart"
}
else
{
holder.binding.textCart.text = ""
}
holder.binding.textID.setOnClickListener {
AlertDialog.Builder(context).setMessage("You clicked ${item.id}.").show()
itemClickListener?.onItemClick(item.id)
}
holder.binding.textName.setOnClickListener {
//AlertDialog.Builder(context).setMessage("You clicked ${student.name}.").show()
itemClickListener?.onItemClick(item.id)
}
//return item.id.toString()
}
override fun getItemCount() = items.size
}
CartActivity.kt
class CartActivity : AppCompatActivity() {
lateinit var binding: ActivityCartBinding
private val db: FirebaseFirestore = Firebase.firestore
private val itemsCollectionRef = db.collection("items")
private var adapter: MyAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityCartBinding.inflate(layoutInflater)
setContentView(binding.root)
updateList()
//binding.recyclerViewItems.layoutManager = LinearLayoutManager(this)
//adapter = MyAdapter(this, emptyList())
//binding.recyclerViewItems.adapter = adapter
binding.changeCartStatus.setOnClickListener{
//change the button's text if the itemID is corrected
//if(){
// binding.changeCartStatus.text = ""
//}
}
}
private fun updateList() {
itemsCollectionRef.get().addOnSuccessListener {
val items = mutableListOf<Item>()
for (doc in it) {
items.add(Item(doc))
}
adapter?.updateList(items)
}
}
}
You just need to implement listener to your activity
class ShoppingAppActivity : AppCompatActivity() ,MyAdapter.OnItemClickListener {
In oncreate add below line after adapter
adapter?.setOnItemClickListener(this)
Then override its method
override fun onItemClick(id: String){
val nextIntent = Intent(this, CartActivity::class.java)
nextIntent.putExtra("itemID",id )
startActivity(nextIntent)
}
I made selectDelete button on the main activity instead of RecyclerView.
When I click that button, I want to delete the items which are checked.
There is an error that checks the RecyclerView item checkbox and unchecks the item when scrolling.
Activity
class CartViewActivity : AppCompatActivity(), SwipeRefreshLayout.OnRefreshListener {
private val tag = this::class.java.simpleName
lateinit var adapter: CartItemRecyclerAdapter
var itemList: MutableList<CartItemDataVo> = arrayListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_cart_view)
selectDelete.setOnClickListener {
if (checkBox.isChecked) {
*adapter.removeItems()*
}
}
swipeRefreshLo.setOnRefreshListener(this)
itemList.add(CartItemDataVo("item1", 1, 16800, "cart_doll", false))
itemList.add(CartItemDataVo("item2", 1, 16800, "cart_cup", false))
itemList.add(CartItemDataVo("item3", 1, 30000, "cart_perfume", false))
itemList.add(CartItemDataVo("item4", 1, 16800, "cart_fan", false))
adapter = CartItemRecyclerAdapter(this, this, itemList)
recycler_view.adapter = adapter
recycler_view.layoutManager =
androidx.recyclerview.widget.LinearLayoutManager(applicationContext)
}
override fun onRefresh() {
swipeRefreshLo.isRefreshing = false
}
}
Adapter:
class CartItemDataVo(
title: String,
itemNumber: Int,
pointValue: Int,
imageView: String,
CheckBox: Boolean
) {
var title: String = title
var itemNumber: Int = itemNumber
var pointValue: Int = pointValue
var image: String = imageView
var isChecked: Boolean = CheckBox
}
class CartItemRecyclerAdapter(
val context: Context,
private var activity: Activity,
private var dataList: MutableList<CartItemDataVo>
) : RecyclerView.Adapter<CartItemRecyclerAdapter.Holder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val view = LayoutInflater.from(context).inflate(R.layout.cart_item_list, parent, false)
return Holder(view)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder?.bind(dataList[position], context)
}
override fun getItemCount(): Int = dataList.size
*#SuppressLint("NewApi")
fun removeItems() {
dataList.removeIf { it.isChecked }
notifyDataSetChanged()
}*
fun toggleItems() {
for (item: CartItemDataVo in dataList) {
var state = item.isChecked
item.isChecked = state.not()
}
notifyDataSetChanged()
}
inner class Holder(itemView: View?) : RecyclerView.ViewHolder(itemView!!) {
var titleText = itemView?.findViewById(R.id.titleText) as TextView
var temNumerTextt = itemView?.findViewById(R.id.textViewItemNumer) as TextView
var pointValueText = itemView?.findViewById(R.id.pointValueText) as TextView
var imageView = itemView?.findViewById(R.id.imageView) as ImageView
var checkBox = itemView?.findViewById(R.id.checkBox) as CheckBox
fun bind(data: CartItemDataVo, context: Context) {
if (data.image != "") {
val resourceId =
context.resources.getIdentifier(data.image, "drawable", context.packageName)
imageView?.setImageResource(resourceId)
} else {
imageView.setImageResource(R.mipmap.ic_launcher)
}
titleText?.text = data.title
temNumerTextt?.text = data.itemNumber.toString()
pointValueText?.text = data.pointValue.toString() + "P"
if (data.isChecked) {
checkBox.buttonDrawable =
checkBox.context.getDrawable(R.drawable.check_box_active_cs)
val layout = activity?.findViewById(R.id.layoutOrder) as LinearLayout
layout.visibility = View.VISIBLE
} else {
checkBox.buttonDrawable = checkBox.context.getDrawable(R.drawable.check_box_no)
val layout = activity?.findViewById(R.id.layoutOrder) as LinearLayout
layout.visibility = View.GONE
}
checkBox?.setOnClickListener {
if (checkBox.isChecked == data.isChecked) {
checkBox.buttonDrawable = it.context.getDrawable(R.drawable.check_box_active_cs)
val layout = activity?.findViewById(R.id.layoutOrder) as LinearLayout
layout.visibility = View.VISIBLE
} else {
checkBox.buttonDrawable = it.context.getDrawable(R.drawable.check_box_no)
val layout = activity?.findViewById(R.id.layoutOrder) as LinearLayout
layout.visibility = View.GONE
}
}
}
}
}
First of all, replace
var itemList: MutableList<CartItemDataVo> = arrayListOf()
with
val itemList: MutableList<CartItemDataVo> = arrayListOf()
You don't want mutable property, which is also mutable collection at the same time. This is very bad practice.
Same situation in adapter
private var dataList : MutableList<CartItemDataVo>
replace with
private val dataList : MutableList<CartItemDataVo>
Then remove private var activity : Activity from your adapter's constructor. Don't put any references to Activity or Fragment in adapter!
Adapter should be only responsible for displaying list.
If in any case, you need communication between Activity or Fragment and adapter, use interface instead. This is not ViewHolder responsibility to hold reference to parent layout and manipulate it!
val layout = activity?.findViewById(R.id.layoutOrder) as LinearLayout
All lines like below should be removed from ViewHolder. If you need set something which belongs to Activity, based on action on list, use interface.
Finally, in adapter add method which will remove checked items from it:
fun removeItems() {
itemList.removeIf { it.isChecked }
notifyDataSetChanged()
}
Add the following line right after checkBox?.setOnClickListener { (as first line in listener)
data.isChecked = !data.isChecked
And replace
selectDelete.setOnClickListener {
if(checkBox.isChecked){
}
}
with
selectDelete.setOnClickListener {
if(checkBox.isChecked){
adapter?.removeItems()
}
}
Bonus:
read about data class, and use it for CartItemDataVo (don't use CheckBox in constructor),
updateData method can be optimized using DiffUtil for RecyclerView,
it can be improved by modifying data in Activity, not in adapter, so responsible would be moved to better place,
read about SOLID principles,
read about MVVM and MVP design patterns.
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" -> {}
}
}