implémenter un écran affichant une liste de tâches et permettre de créer des nouvelles tâches.

Vous allez créer un unique projet que vous mettrez à jour au fur à mesure des TPs:

Dans ./build.gradle (celui du projet), vérifiez que kotlin_version est récent (ex: 1.5.31)

Dans app/build.gradle.kts (celui du module app), ajouter les libs suivante dans dependencies {} (si l'IDE vous le propose, mettez à jour les numéros de version):

implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation("androidx.fragment:fragment-ktx:1.6.2")
implementation("androidx.activity:activity-ktx:1.8.1")

📁 Les fichiers source Java ou Kotlin sont rangés en "packages":

Créez un nouveau package list à l'intérieur votre package source de base (pas à côté !) :

clic droit sur 'com.nicoalex.todo' > new > package > tapez 'list'

Vous y mettrez tous les fichiers source (Kotlin) concernant la liste de tâches

class TaskListFragment : Fragment() {}
val rootView = inflater.inflate(R.layout.fragment_task_list, container, false)
private var taskList = listOf("Task 1", "Task 2", "Task 3")

Cette activity va servir de conteneur de fragments:

Dans activity_main.xml, remplacez la balise TextView par celle ci et adaptez:

 <androidx.fragment.app.FragmentContainerView
    android:name="com.nicoalex.todo.list.TaskListFragment"
    android:id="@+id/fragment_tasklist"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
// l'IDE va râler ici car on a pas encore implémenté les méthodes nécessaires
class TaskListAdapter : RecyclerView.Adapter<TaskListAdapter.TaskViewHolder>() {

  var currentList: List<String> = emptyList()

  // on utilise `inner` ici afin d'avoir accès aux propriétés de l'adapter directement
  inner class TaskViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    fun bind(taskTitle: String) {
      // on affichera les données ici
    }
  }
}
private val adapter = TaskListAdapter()
adapter.currentList = taskList
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
val recyclerView = view.findViewById<RecyclerView>(R.id.id_de_votre_recycler_view)
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="wrap_content">

  <TextView
      android:id="@+id/task_title"
      android:background="@android:color/holo_blue_bright"
      android:layout_width="match_parent"
      android:layout_height="wrap_content" />
</LinearLayout>

Dans TaskListAdapter, implémenter toutes les méthodes requises:

Astuce: Pré-remplissez votre adapter en cliquant sur le nom de votre classe (qui doit être pour l'instant soulignée en rouge) et cliquez sur l'ampoule jaune ou tapez Alt + ENTER (sinon, CTRL/CMD + o n'importe où dans la classe)

val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_task, parent, false)
private var taskList = listOf(
   Task(id = "id_1", title = "Task 1", description = "description 1"),
   Task(id = "id_2", title = "Task 2"),
   Task(id = "id_3", title = "Task 3")
)

Utilisez .setOnClickListener {} sur le bouton d'ajout pour ajouter une tâche à votre liste à chaque fois qu'on clique dessus:

// Instanciation d'un objet task avec des données préremplies:
val newTask = Task(id = UUID.randomUUID().toString(), title = "Task ${taskList.size + 1}")
taskList = taskList + newTask

Cette façon de "notifier" manuellement n'est pas idéale, il existe en fait une sous-classe de RecyclerView.Adapter qui permet de gérer cela automatiquement: ListAdapter

Améliorer l'implémentation de TasksListAdapter en héritant de ListAdapter au lieu de RecyclerView.Adapter

Il faudra notamment: créer un DiffUtil.ItemCallback et le passer au constructeur parent, supprimer getItemCount et la propriété currentList car ils sont déjà définis dans ListAdapter

Exemple:

object MyItemsDiffCallback : DiffUtil.ItemCallback<MyItem>() {
   override fun areItemsTheSame(oldItem: MyItem, newItem: MyItem) : Boolean {
      return // comparaison: est-ce la même "entité" ? => même id?
   }

   override fun areContentsTheSame(oldItem: MyItem, newItem: MyItem) : Boolean {
      return // comparaison: est-ce le même "contenu" ? => mêmes valeurs? (avec data class: simple égalité)
   }
}

class ItemListAdapter : ListAdapter<Item, ItemListAdapter.ItemViewHolder>(ItemsDiffCallback) {
   override fun onCreateViewHolder(...)
   override fun onBindViewHolder(...)
}

// Usage is simpler:
val adapter = ItemListAdapter()
recyclerView.adapter = adapter
adapter.submitList(listOf("Item#1", "Item #2"))

Utiliser le ViewBinding (documentation / slides dans TaskListFragment:

Puis faites pareil pour les ViewHolder: c'est un peu plus complexe, il faudra changer le constructeur pour qu'il prenne un val binding: ItemTaskBinding afin d'y avoir accès dans le corps de la classe et passer binding.root au constructeur parent.