Créé en 2020 et modifié le 18 May 2021

Créé en 2020 et modifié le 18 May 2021

Android kotlin – les listes

Intro

  • réaliser le diagramme de classe permettant de décrire l’interaction entre les layouts, les Activity et Adapter des parties 1 et 2.

Partie 1 – RecyclerView

Livrable : oless/mission4/…

 

On va afficher une liste de villes dans une liste déroulante performante

  • ajouter dans build.gradle(app)
dependencies {

...
      implementation "androidx.recyclerview:recyclerview:1.1.0"
}
  • ajouter le layout list_ville correspondant à la liste d’item
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/villes_recycler_view"
            android:layout_height="match_parent"
            android:layout_width="match_parent" />
</RelativeLayout>
  • ajouter le layout item_ville d’un item
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="50dp">

    <TextView
        android:id="@+id/item_nom"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
</RelativeLayout>
  • ajouter une classe VilleAdapter qui permet d’afficher chaque élement de la liste
class VilleAdapter(val villes: Array<String>) : RecyclerView.Adapter<VilleAdapter.ViewHolder>() {
   
    class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){
        val nom=itemView.findViewById(R.id.item_nom) as TextView   
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val viewItem = inflater.inflate(R.layout.item_ville,parent,false)
        return ViewHolder(viewItem)
    }

    override fun getItemCount(): Int {
        return villes.size
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val ville= villes[position]
        holder.nom.text=ville

    }
}

 

 

  • ajouter dans l’activité VilleActivity
class MainActivity : AppCompatActivity() {

    var villes=arrayOf<String>("Erceville",
    "Ercheu",
    "Erdeven",
    "Ergersheim",
    "Ergny",
    "Ergue-Gaberic",
    "Erize-Saint-Dizier",
    "Ermenonville",
    "Ermont",
    "Ernee",
    "Ernemont-sur-Buchy",
    "Ernestviller",
    "Ernolsheim-Bruche",
    "Erome",
    "Eroudeville",
    "Erquinghem-Lys",
    "Erquinvillers",
    "Erquy",
    "Erre",
    "Errouville",
    "Erstein",
    "Ervauville",
    "Esbarres",
    "Esbly",
    "Escalquens",
    "Escames",
    "Escassefort",
    "Escaudain",
    "Escaudoeuvres",
    "Escautpont",
    "Escazeaux",
    "Eschau",
    "Eschbach-au-Val",
    "Eschentzwiller",
    "Esches",
    "Esclainvillers",
    "Escolives-Sainte-Camille",
    "Escombres-et-le-Chesnois",
    "Escondeaux",
    "Escorneboeuf",
    "Escou",
    "Escout",
    "Escoutoux",
    "Escurolles",
    "Esery",
    "Eslettes",
    "Esmery-Hallon",
    "Esnandes",
    "Esnouveaux",
    "Espagnac",
    "Espalais",
    "Espalion",
    "Espaly-Saint-Marcel",
    "Esparron-de-Verdon",
    "Espedaillac",
    "Espelette",
    "Espeluche",
    "Espezel",
    "Espiet",
    "Espinasses",
    "Espira-de-Conflent",
    "Espirat",
    "Espondeilhan",
    "Esquay-Notre-Dame",
    "Esquay-sur-Seulles",
    "Esquelbecq",
    "Esquerchin",
    "Esquerdes");
    val adapter = VilleAdapter(villes)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.list_ville )
       villes_recycler_view!!.layoutManager=LinearLayoutManager(this)
       villes_recycler_view!!.adapter=adapter
 }
}
  • ajouter un bouton à l’activité principale pour voir VilleActivity
  • ajouter un menu

Partie 2 – les contacts

Livrable : oless/mission4/…

On va récupérer les contacts du téléphone et les afficher

  • ajouter la permission au manifest
<uses-permission android:name="android.permission.READ_CONTACTS" />
  • réaliser la classe Contact qui a deux propriétés nom et numero
data class Contact(val nom : String= "", val numero : String):
    Parcelable {

    constructor(parcel: Parcel) : this(
        parcel.readString().toString(),
        parcel.readString().toString()
    ) {
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(nom)
        parcel.writeString(numero)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<Contact> {
        override fun createFromParcel(parcel: Parcel): Contact {
            return Contact(parcel)
        }

        override fun newArray(size: Int): Array<Contact?> {
            return arrayOfNulls(size)
        }
    }
}
  • réaliser le layout item_contact qui permet d’afficher une ligne
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="30dp"
    android:orientation="horizontal">

    <TextView
    android:id="@+id/item_nom"
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:layout_weight="0.2" 
        android:layout_marginLeft="1dp"
    />
    <TextView
        android:id="@+id/item_numero"
        android:layout_width="0dp"
        android:layout_weight="0.2"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/item_nom"
        android:layout_marginLeft="10dp"
        />
</LinearLayout>
  • réaliser le layout list_contact qui permet d’afficher la liste
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/contacts_recycler_view"
    android:layout_height="match_parent"
    android:layout_width="match_parent" />
</RelativeLayout>
  • réaliser la classe ContactAdapter qui permet d’afficher un item
class ContactAdapter(val contacts : ArrayList<Contact>) : RecyclerView.Adapter<ContactAdapter.ViewHolder>() {

    class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){
        val nom=itemView.findViewById(R.id.item_nom) as TextView
        val numero=itemView.findViewById(R.id.item_numero) as TextView
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val viewItem = inflater.inflate(R.layout.item_contact,parent,false)
        return ViewHolder(viewItem)
    }

    override fun getItemCount(): Int {
        return contacts.size
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val contact = contacts[position]
        holder.nom.text=contact.nom
        holder.numero.text=contact.numero

    }
}
  • réaliser une activité Contact Activity qui permette de récupérer depuis le téléphone les contacts en demandant l’autorisation et d’afficher la liste
class ContactActivity : AppCompatActivity() {

    companion object {
        val PERMISSIONS_REQUEST_READ_CONTACTS = 100
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        loadContacts()
    }

    private fun loadContacts() {
        var contactsX= arrayListOf<Contact>()

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(
                Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(arrayOf(Manifest.permission.READ_CONTACTS),
                PERMISSIONS_REQUEST_READ_CONTACTS)
            //callback onRequestPermissionsResult
        } else {
            contactsX = getContacts()
            val adapter = ContactAdapter(contactsX)
            setContentView(R.layout.list_contact)
            contacts_recycler_view!!.layoutManager= LinearLayoutManager(this)
            contacts_recycler_view!!.adapter=adapter


        }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>,
                                            grantResults: IntArray) {
        if (requestCode == PERMISSIONS_REQUEST_READ_CONTACTS) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                loadContacts()
            } else {
                 Toast.makeText(this,"Permission must be granted in order to display contacts information",Toast.LENGTH_LONG).show()
            }
        }
    }

    private fun getContacts(): ArrayList<Contact> {
        var contactsX= arrayListOf<Contact>()
        val resolver: ContentResolver = contentResolver;
        val cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null,
            null)

        if (cursor!!.count > 0) {
            while (cursor.moveToNext()) {
                val id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID))
                val name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
                val phoneNumber = (cursor.getString(
                    cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))).toInt()

                if (phoneNumber > 0) {
                    val cursorPhone = contentResolver.query(
                        ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                        null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?", arrayOf(id), null)

                    if(cursorPhone!!.count > 0) {
                        while (cursorPhone.moveToNext()) {
                            val phoneNumValue = cursorPhone.getString(
                                cursorPhone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
                            contactsX.add(Contact(name,phoneNumValue))

                        }
                    }
                    cursorPhone.close()
                }
            }
        } else {
            Toast.makeText(this,"pas de contact",Toast.LENGTH_LONG).show()
        }
        cursor.close()
        return contactsX
    }
}

 

  • ajouter des contacts au téléphone, on pourra connecter son compte pour les importer automatiquement.
  • modifier votre MainActivity pour afficher ContactActivity

 

Amélioration de la liste

On va améliorer l’affichage et récupérer l’élément de la liste au clique.

  • ajouter la dépendance cardView  dans build.gradle
implementation "androidx.cardview:cardview:1.0.0"

 

  • modifier item_contact, on ajoute le cardView et l’écoute d’événement
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:orientation="horizontal">

    <androidx.cardview.widget.CardView
        android:id="@+id/card_view"
        android:layout_width="match_parent"
        android:layout_height="38dp"
        android:clickable="true"
        android:focusable="true"
        android:foreground="?android:attr/selectableItemBackground">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center">

            <TextView
                android:id="@+id/item_nom"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginLeft="8dp"
                android:layout_weight="0.2" />

            <TextView
                android:id="@+id/item_numero"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:layout_toRightOf="@id/item_nom"
                android:layout_weight="0.2"

                />
        </LinearLayout>
    </androidx.cardview.widget.CardView>
</LinearLayout>

 

  • modifier ContactAdapter pour gérer l’évenement
class ContactAdapter(val contacts : ArrayList<Contact>, /*new*/ val itemClickListener: View.OnClickListener) : RecyclerView.Adapter<ContactAdapter.ViewHolder>() {

    class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){
        /*new*/
        val cardView=itemView.findViewById(R.id.card_view) as CardView
        val nom=itemView.findViewById(R.id.item_nom) as TextView
        val numero=itemView.findViewById(R.id.item_numero) as TextView
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val viewItem = inflater.inflate(R.layout.item_contact,parent,false)
        return ViewHolder(viewItem)
    }

    override fun getItemCount(): Int {
        return contacts.size
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val contact = contacts[position]
        holder.nom.text=contact.nom
        holder.numero.text=contact.numero

        /*new*/
        holder.cardView.tag=position
        holder.cardView.setOnClickListener(itemClickListener)

    }
  • modifier ContactActivity  qui récupère l’événement de l’affiche
class ContactActivity : AppCompatActivity(),/*new*/ View.OnClickListener {

    companion object {
        val PERMISSIONS_REQUEST_READ_CONTACTS = 100
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        loadContacts()
    }

    private fun loadContacts() {
        var contactsX= arrayListOf<Contact>()

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(
                Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(arrayOf(Manifest.permission.READ_CONTACTS),
                PERMISSIONS_REQUEST_READ_CONTACTS)
            //callback onRequestPermissionsResult
       } else {
            contactsX = getContacts()
            /*new*/
            val adapter = ContactAdapter(contactsX,this)
            setContentView(R.layout.list_contact)
            contacts_recycler_view!!.layoutManager= LinearLayoutManager(this)
            contacts_recycler_view!!.adapter=adapter


        }
    }

   override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>,
                                            grantResults: IntArray) {
        if (requestCode == PERMISSIONS_REQUEST_READ_CONTACTS) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                loadContacts()
            } else {
                Toast.makeText(this,"Permission must be granted in order to display contacts information",Toast.LENGTH_LONG).show()
              
            }
        }
    }

    var contactsX= arrayListOf<Contact>()
    private fun getContacts(): ArrayList<Contact> {

        val resolver: ContentResolver = contentResolver;
        val cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null,
            null)

        if (cursor!!.count > 0) {
            while (cursor.moveToNext()) {
                val id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID))
                val name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
                val phoneNumber = (cursor.getString(
                    cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))).toInt()

                if (phoneNumber > 0) {
                    val cursorPhone = contentResolver.query(
                        ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                        null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?", arrayOf(id), null)

                    if(cursorPhone!!.count > 0) {
                        while (cursorPhone.moveToNext()) {
                            val phoneNumValue = cursorPhone.getString(
                                cursorPhone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
                            contactsX.add(Contact(name,phoneNumValue))

                        }
                    }
                    cursorPhone.close()
                }
            }
        } else {
            Toast.makeText(this,"pas de contact",Toast.LENGTH_LONG).show()
        }
        cursor.close()
        return contactsX
    }
    /*new*/
    override fun onClick(view: View) {
         if(view.tag !=null){
             val index=view.tag as Int
             val contact=contactsX[index]
             Toast.makeText(this,"${contact.nom}",Toast.LENGTH_SHORT).show()

         }
    }
}
  • afficher le numéro de téléphone dans le Toast plutôt que le nom