Ejemplo de implementación de CRUD (Create Read Update Delete) sobre un ListAdapter integrado en un RecyclerView usando Kotlin.
El modelo a mostrar es simple, titulo y un contador numerico, que se actualizará cada vez que es pulsado el elemento
data class CounterTask(var title : String, var clicks : Int = 0)
- Pulsar FabButton se añade un elemento a la lista, con un titulo aleatorio
- Pulsar sobre un elemento se incrementará el valor númerico
- Pulsación larga sobre un elemento se eliminará de la lista
Layout de representación de los datos `simple_list_item_2.xml`
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="56dp"
android:orientation="vertical"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:paddingStart="?attr/listPreferredItemPaddingStart"
android:paddingEnd="?attr/listPreferredItemPaddingEnd">
<TextView
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="?attr/textAppearanceListItem"
tools:text="Line one" />
<TextView
android:id="@android:id/text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:textColorSecondary"
android:textAppearance="?attr/textAppearanceListItemSecondary"
tools:text="Line two" />
</LinearLayout>
Layout del fragmento con el recyclerview fragment_list.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@drawable/ic_add_24dp" />
</FrameLayout>
Adaptador ListAdapter
Adaptador MyListAdapter que implementa la funcionalidad de pulsar sobre cada elemento con el OnItemClickListener
class MyListAdapter(private val itemClickListener: OnItemClickListener) :
ListAdapter<CounterTask, MyListAdapter.MyViewHolder>(DiffCallback()) {
private class DiffCallback : DiffUtil.ItemCallback<CounterTask>() {
override fun areItemsTheSame(oldItem: CounterTask, newItem: CounterTask): Boolean {
return oldItem.title == newItem.title
}
override fun areContentsTheSame(oldItem: CounterTask, newItem: CounterTask): Boolean {
return oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val v = LayoutInflater.from(parent.context)
.inflate(R.layout.simple_list_item_2, parent, false)
return MyViewHolder(v)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(getItem(position), itemClickListener)
}
inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val tvText1 = itemView.findViewById(android.R.id.text1) as TextView
private val tvText2 = itemView.findViewById(android.R.id.text2) as TextView
fun bind(item: CounterTask, clickListener: OnItemClickListener) {
tvText1.text = item.title
tvText2.text = item.clicks.toString()
itemView.setOnClickListener { clickListener.onItemClick(adapterPosition, item) }
itemView.setOnLongClickListener {
clickListener.onItemLongClick(adapterPosition, item)
return@setOnLongClickListener false
}
}
}
interface OnItemClickListener {
fun onItemClick(position: Int, item: CounterTask)
fun onItemLongClick(position: Int, item: CounterTask)
}
}
Inicialización del RecyclerView (Read)
A continuación el código de carga de datos, se almacenarán en la variable dataSet
private var dataSet: MutableList<CounterTask> = arrayListOf()
private lateinit var mAdapter: MyListAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initRecyclerView()
...
}
private fun initRecyclerView() {
mAdapter = MyListAdapter(this)
rvList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
rvList.adapter = mAdapter
}
Añadir datos (Create)
en onViewCreated añadir la detección de pulsar sobre el fabAdd y añade un elemento a la lista y actualice los datos con submitList
fabAdd.setOnClickListener {
dataSet.add(CounterTask(randomStr()))
//Update new list
mAdapter.submitList(dataSet)
mAdapter.notifyDataSetChanged()
}
Actualizar datos (Update)
Al pulsar sobre un elemento de la lista, actualice el contador, se añade la intercepción del evento MyListAdapter.OnItemClickListener
class ListFragment : Fragment(), MyListAdapter.OnItemClickListener
...
override fun onItemClick(position: Int, item: CounterTask) {
Log.d(TAG, "onItemClick($position) item(${item.toString()})")
val item = dataSet[position]
item.clicks = item.clicks + 1
dataSet[position] = item
mAdapter.submitList(dataSet)
mAdapter.notifyDataSetChanged()
}
Eliminar datos (Delete)
Al realizar una pulsación larga sobre el elemento, se elimine y actualice la lista
override fun onItemLongClick(position: Int, item: CounterTask) {
Log.d(TAG, "onItemLongClick($position) item(${item.toString()})")
dataSet.removeAt(position)
mAdapter.submitList(dataSet)
mAdapter.notifyDataSetChanged()
}
Animar acción
Si queremos añadir animación al añadir elementos, actualizar o eliminar, se debe añadir animateLayoutChanges a true en el layout padre
android:animateLayoutChanges="true"