Skip to main content
À partir du 30 octobre 2026, la Trailblazer Community en ligne sera disponible uniquement en lecture seule. Les nouvelles conversations de communauté seront déplacées sur Slack. Consulter notre FAQ

Modification de l’application native Forcedroid

Objectifs de formation

Une fois cette unité terminée, vous pourrez :

  • Modifier l'application modèle Android native pour personnaliser l'écran de la liste
  • Traiter une réponse REST
  • Supprimer un enregistrement Salesforce via une requête REST

Personnalisation de l'écran de la liste

Comme vu précédemment, l’application modèle Android native affiche une liste de contacts et de comptes depuis l’organisation Salesforce de l’utilisateur. Actuellement, cette liste en lecture seule est générée à partir d’une simple requête SOQL SELECT traitée par les classes REST du kit de développement Mobile SDK.

Ajoutons quelques capacités de modification en associant une action de suppression en cas de pression longue. Lorsque l’utilisateur touche et maintient un nom de contact dans la vue de liste, votre nouvelle action de suppression tente de supprimer l’enregistrement Salesforce associé. Si cette tentative réussit, votre application supprime définitivement la ligne de la vue de liste Contacts dans l'application. Si la requête échoue, votre application indique à l'utilisateur les raisons de cet échec et rétablit la ligne de la vue de liste.

À propos du module d'écoute du clic prolongé

L’implémentation de ce module est simple. Mais décider comment créer une classe pour le module d'écoute du clic prolongé est un peu plus délicat. Si vous explorez les différentes options de codage, la première chose que vous découvrirez, c'est que vous ne prêtez pas attention aux clics au niveau de la vue de liste. Vous le faites au niveau des éléments de la liste.

Par chance, l’implémentation de modules d’écoute pour les éléments de la liste n’est pas une tâche difficile, grâce à l’interface Android OnItemLongClickListener. Cette interface définit un seul module d'écoute associé à la vue de liste et répond aux pressions longues sur un élément de la liste. Vous créez une instance de cette classe en implémentant une interface publique au sein de votre classe de vue.

Le prochain défi est de déterminer la classe de vue qui implémente le module d'écoute du clic prolongé. Grâce aux objets ListView, vous fournissez un objet de données qui donne les informations qui s’affichent dans la liste. L’application modèle du kit de développement Mobile SDK crée, dans cette optique, un objet ArrayAdapter<String>, puis le rattache à ListView.

listAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, ArrayList())
findViewById<ListView>(R.id.contacts_list).adapter = listAdapter

Toutefois, ArrayAdapter est un objet de données et non pas une vue, n’est-ce pas ? Oui, mais pas seulement. Dans la vue de liste Contacts, ArrayAdapter crée un objet AdapterView pour chaque élément de l’ensemble de données de la liste. Les vues de cet adaptateur représentent les objets qui vous intéressent. Par conséquent, utilisez la classe AdapterView pour implémenter OnItemLongClickListener.

Il vous faut ensuite associer l’objet de module d’écoute à l’objet ListView, qui reçoit toutes les notifications pour ses enfants. Cette association limite votre OnItemLongClickListener pour qu’il interagisse uniquement avec les éléments de la vue de liste de l’application modèle. Enfin, vous exécutez votre comportement de suppression dans la seule méthode d'interface.

Un dernier détail à résoudre : où placez-vous ce code pour le module d'écoute du clic prolongé ? Le kit de développement Mobile SDK vous propose les méthodes de rappel de point d’entrée suivantes :

public abstract void onResume(RestClient client);
@Override
protected void onCreate(Bundle savedInstanceState);
@Override
public void onResume();

Nous pouvons en éliminer une : onCreate(Bundle savedInstanceState). Cette méthode configure l'activité et gère les flux d'authentification avant l’instanciation des vues. Les vues entrent en action dans la méthode onResume(). Cette méthode semble donc être la plus probable. La méthode onResume(RestClient client) est appelée par la super-classe lors de la connexion pour capturer l’objet RestClient authentifié, que nous allons laisser de côté ici. Par conséquent, la réponse est maintenant évidente : vous devez ajouter votre code de module d’écoute du clic prolongé à onResume().

Implémentation d’un module d’écoute de clic prolongé de base

Commençons à coder. Dans Android Studio, ouvrez votre classe MainActivity et observez la méthode onResume().

  1. Dans Android Studio, ouvrez votre fichier MainActivity.kt.
  2. Trouvez la méthode onResume().
    override fun onResume() {
    // Hide everything until we are logged in
    findViewById<ViewGroup>(root).visibility = View.INVISIBLE
    // Create list adapter
    listAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, ArrayList())
    findViewById<ListView>(R.id.contacts_list).adapter = listAdapter
    // ADD CODE HERE!
    super.onResume()
}

Vous allez commencer le codage comme indiqué, à savoir après la définition de listAdapter pour ListView, mais avant l’appel super.onResume().

  1. Déclarez et attribuez une variable pratique ListView, qui référence la vue de liste Contacts. Utilisez la méthode Activity.findViewById() pour référencer la ressource de vue de liste.
    findViewById<ListView>(R.id.contacts_list).adapter = listAdapter
val listView = findViewById<ListView>(R.id.contacts_list)
super.onResume()
  1. En utilisant la variable ListView, appelez la fonction lambda AdapterView.setOnItemLongClickListener() pour configurer un module d’écoute d’événements de clic prolongé. Pour le paramètre du module d’écoute, instanciez un stub en ligne de l’interface AdapterView.OnItemLongClickListener.
    val listView = findViewById<ListView>(R.id.contacts_list)
listView.setOnItemLongClickListener { parent, view, position, id -> false }
  1. (Facultatif) Si une erreur relative à une importation manquante s’affiche, ajoutez import android.widget.AdapterView à vos importations de classe.
  2. Dans le corps AdapterView.OnItemLongClickListener, remplacez l’instruction return standard par le code qui présente un message de confirmation.
    val listView = findViewById<ListView>(R.id.contacts_list)
listView.setOnItemLongClickListener { parent, view, position, id ->
    Toast.makeText(
        applicationContext,
        "Long press received",
        Toast.LENGTH_SHORT,
    ).show()
    true
}
  1. Compilez l’application, puis exécutez-la.
  2. Lorsque vous êtes connecté à l’application, cliquez sur Fetch Contacts.
  3. Touchez une entrée dans la liste Contacts et maintenez appuyé pendant quelques secondes. Si tout est correct, le message toast s’affiche.

Ajout de requêtes REST du kit de développement Mobile SDK

Vous allez bientôt pouvoir ajouter des éléments issus du kit de développement Mobile SDK. Dans la méthode onItemLongClick(), vous créez une requête REST pour supprimer l’enregistrement Salesforce associé à la ligne touchée. Vous envoyez ensuite cette requête à Salesforce. Avant d’explorer le code, examinons quelques points importants.

Obtention d’une instance RestClient

N’oubliez pas que vous ne créez jamais directement des objets RestClient. Le kit de développement Mobile SDK en crée un pour vous et le renvoie à MainActivity via la méthode onResume(RestClient client). Cette instance RestClient est authentifiée avec le jeton d’accès de l’utilisateur actif. Pour votre utilisation, la méthode onResume(RestClient client) attribue cette instance à la variable de classe client.

Création d’une requête REST

Pour créer la requête REST de cet exercice, vous devez appeler la méthode de fabrique RestRequest permettant de supprimer un enregistrement :

public static RestRequest getRequestForDelete(String apiVersion, String objectType, String objectId);

Où trouver les valeurs d'argument ? Examinez le tableau ci-dessous.

Paramètre

Valeur

apiVersion

ApiVersionStrings.getVersionNumber(Context context)

objectType

“Contact” (codé en dur)

objectId

? ?

Le paramètre objectId pose problème. Il nécessite une valeur Salesforce que votre application forcedroid brute ne reconnaît pas. Pourquoi ne l'avez-vous pas ? Que pouvez-vous faire pour l'avoir ? Les réponses à ces questions sont simples.

  • Vous n'avez pas les ID, car les requêtes REST d'origine (celles de la liste) ne les demandent pas.
  • Vous pouvez obtenir l'ID en modifiant les requêtes REST.

Ajustement de la requête SOQL du modèle d’application

Votre classe MainActivity émet deux requêtes REST : une pour les contacts et une autre pour les comptes. Chaque requête contient une instruction SOQL. Vous n’allez pas utiliser de comptes. Par conséquent, mettez à jour la requête relative aux enregistrements de contact pour qu’elle renvoie des valeurs d’ID.

  1. Dans Android Studio, ouvrez le fichier MainActivity.kt.
  2. Recherchez la méthode onFetchContactsClick().
    fun onFetchContactsClick(v: View) {
    sendRequest("SELECT Name FROM Contact")
}
  1. Modifiez la requête SOQL afin de sélectionner les champs Name (Nom) et Id (ID). Assurez-vous de respecter la casse mixte du nom de champ de l'ID. Les noms de champs sont sensibles à la casse.
    fun onFetchContactsClick(v: View) {
    sendRequest("SELECT Name, Id FROM Contact")
}

Vous êtes prêt à recevoir les valeurs d’ID dans la réponse REST. Quel est le meilleur emplacement pour les stocker ? L'application modèle copie simplement la valeur Nom de chaque enregistrement dans une ligne de la vue de liste, elle ne met en cache les valeurs d’ID. Pour activer les références dans votre gestionnaire, vous devez stocker les ID dans une portée de classe.

Adaptation de la méthode sendRequest() de l’application modèle

Faites défiler l’écran jusqu’à la méthode sendRequest(String soql), qui correspond à l’endroit où arrive la réponse extraite. Cette méthode démontre clairement la façon dont fonctionne le mécanisme REST du kit de développement Mobile SDK. Jetons-y un rapide coup d'œil. Tout d’abord, la méthode appelle une méthode de fabrique RestRequest, qui définit une requête REST pour une requête SOQL donnée :

val restRequest = RestRequest.getRequestForQuery(ApiVersionStrings.getVersionNumber(this), soql)

Elle envoie ensuite le nouvel objet RestRequest à Salesforce dans l’appel client.sendAsync().

client!!.sendAsync(restRequest, object : AsyncRequestCallback {
    override fun onSuccess(request: RestRequest, result: RestResponse) {
        result.consumeQuietly() // consume before going back to main thread
        runOnUiThread {
            try {
                listAdapter!!.clear()
                val records = result.asJSONObject().getJSONArray("records")
                for (i in 0..<records.length()) {
                    listAdapter!!.add(records.getJSONObject(i).getString("Name"))
                }
            } catch (e: Exception) {
                onError(e)
            }
        }
    }
    override fun onError(exception: Exception) {
        runOnUiThread {
            Toast.makeText(this@MainActivity,
                    this@MainActivity.getString(R.string.sf__generic_error),
                    Toast.LENGTH_LONG).show()
        }
    }
})

En plus de l’objet RestRequest, l’appel sendAsync() nécessite d’implémenter l’interface AsyncRequestCallback du kit de développement Mobile SDK. La méthode onSuccess() de cette interface reçoit la réponse REST de façon asynchrone via un rappel. Votre implémentation par défaut AsyncRequestCallback traite uniquement les requêtes SOQL définies dans votre application forcedroid.

Cette méthode onSuccess() fait déjà ce dont nous avons besoin. Elle contient le code qui extrait les enregistrements renvoyés par la réponse REST et les attribue à la variable locale des enregistrements. Déplaçons cette variable vers l’étendue de la classe en la redéclarant hors du corps de la méthode.

  1. En haut de la définition de la classe MainActivity, déclarez les enregistrements JSONArray en tant que variable privée avec les déclarations de la variable de classe existante :
    class MainActivity : SalesforceActivity() {
    private var client: RestClient? = null
    private var listAdapter: ArrayAdapter<String>? = null
    private lateinit var records: JSONArray
   …
  1. Dans la méthode onSuccess(), supprimez la déclaration de type pour la variable d’enregistrement :
    override fun onSuccess(request: RestRequest, result: RestResponse) {
    result.consumeQuietly() // consume before going back to main thread
    runOnUiThread {
        try {
            listAdapter!!.clear()
            records = result.asJSONObject().getJSONArray("records")
            for (i in 0..<records.length()) {
                listAdapter!!.add(records.getJSONObject(i).getString("Name"))
            }
        } catch (e: Exception) {
            onError(e)
        }
    }
}

Finalisation de la méthode onItemLongClick()

Vous voilà prêt à finaliser la méthode onItemLongClick(). L’algorithme de base est présenté ci-après.

  1. Obtenir un objet de « demande de suppression » en appelant une méthode de fabrique appropriée du kit de développement Mobile SDK RestRequest. Toutes les méthodes RestRequest renvoient des exceptions, assurez-vous donc de placer l’appel dans un bloc try…catch.
  2. Envoyer l’objet de « demande de suppression » à Salesforce à l’aide de l’objet RestClient généré.
  3. Traiter le résultat REST dans les méthodes de rappel.

Obtention d'un objet RestRequest

  1. Revenez à la méthode setOnItemLongClickListener dans la méthode onResume().
  2. Après l’appel Toast.makeText(), déclarez un objet local RestRequest nommé restRequest.
var restRequest: RestRequest
  1. Ajoutez une construction try…catch vide.
try {
} catch (e: Exception) {
}
  1. Dans le bloc try, appelez une méthode de fabrique qui obtient un objet de requête REST relatif à une opération de suppression. ASTUCE : utilisez la méthode statique RestRequest.getRequestForDelete().
try {
    restRequest = RestRequest.getRequestForDelete(
        // arguments?
    )
} catch (e: Exception) {
}
  1. Pour le premier paramètre, récupérez la version de l’API Salesforce spécifiée dans les ressources Kit de développement mobile de votre projet.
try {
    restRequest = RestRequest.getRequestForDelete(
        /* apiVersion = */ ApiVersionStrings.getVersionNumber(this),
        // ..
    )
} catch (e: Exception) {
}
  1. Pour le paramètre objectType, spécifiez "Contact".
try {
    restRequest = RestRequest.getRequestForDelete(
        /* apiVersion = */ ApiVersionStrings.getVersionNumber(this),
        /* objectType = */ "Contact",
        // ..
    )
} catch (e: Exception) {
}
  1. Transmettez à RestRequest.getRequestForDelete() l’ID de l’entrée dans le tableau des enregistrements qui correspond à la position actuelle de la vue de liste. Voici comment récupérer l’ID.
try {
    restRequest = RestRequest.getRequestForDelete(
        /* apiVersion = */ ApiVersionStrings.getVersionNumber(this),
        /* objectType = */ "Contact",
        /* objectId = */ records.getJSONObject(position).getString("Id"),
    )
} catch (e: Exception) {
}
  1. Dans le bloc catch, appelez printStackTrace() sur l’argument Exception.
try {
    restRequest = RestRequest.getRequestForDelete(
        /* apiVersion = */ ApiVersionStrings.getVersionNumber(this),
        /* objectType = */ "Contact",
        /* objectId = */ records.getJSONObject(position).getString("Id"),
    )
    // Send the request
    // ...
} catch (e: Exception) {
    e.printStackTrace()
}

Dès que vous avez obtenu l’objet « requête de suppression », envoyez-le à Salesforce, puis gérez le résultat dans les méthodes de rappel.

Ajout de la méthode RestClient.sendAsync()

Vous y êtes presque ! La dernière étape consiste à envoyer la requête à l’aide de la méthode RestClient.sendAsync(). Cette méthode nécessite d’implémenter l’interface AsyncRequestCallback. Comme vous le savez, le kit de développement Mobile SDK envoie les réponses REST à vos méthodes AsyncRequestCallback.

  1. Dans onItemLongClick(), après l’appel getRequestForDelete(), copiez et collez le code RestClient.sendAsync() de la méthode sendRequest().
  2. Supprimez le code interne de la branche try de la méthode onSuccess(). Ne supprimez pas la branche catch, car elle fait simplement appel à un gestionnaire d’erreur.
  3. Conservez l’implémentation de remplacement onError() : elle est suffisamment générique pour fonctionner avec n’importe quelle réponse Salesforce.

Voici la version remplacée de l’appel à RestClient.sendAsync().

restRequest = RestRequest.getRequestForDelete(
    /* apiVersion = */ ApiVersionStrings.getVersionNumber(this),
    /* objectType = */ "Contact",
    /* objectId = */ records.getJSONObject(position).getString("Id"),
)
client!!.sendAsync(restRequest, object : AsyncRequestCallback {
    override fun onSuccess(request: RestRequest?, result: RestResponse) {
        result.consumeQuietly()
        runOnUiThread {
            // The network component doesn’t report app layer status.
            // Use Mobile SDK RestResponse.isSuccess() method to check
            // whether the REST request itself succeeded.
            if (result.isSuccess) {
                try {
                } catch (e: Exception) {
                    onError(e)
                }
            }
        }
    }
   override fun onError(exception: Exception) {
        runOnUiThread {
            Toast.makeText(this@MainActivity,
                    getString(R.string.sf__generic_error),
                    Toast.LENGTH_LONG,
            ).show()
        }
    }
})

Implémentation de la méthode de rappel onSuccess()

Dans la méthode onSuccess() de AsyncRequestCallback(), vous effectuez les opérations suivantes.

  1. Assurez-vous que l’opération de suppression a réussi en testant le statut HTTP. Cette vérification est nécessaire, car le composant de communauté sous-jacent renvoie uniquement les échecs de la couche de transport, pas les échecs de requêtes REST.
  2. Si l’opération réussit, retirez l’élément à la position indiquée de la vue de liste.
  3. Vous publiez un message de réussite.

Vous utilisez la variable de classe listAdapter pour supprimer la ligne. Vous pouvez appeler ArrayAdapter.remove(T object), à l’aide de la valeur de position transmise à la méthode onItemLongClick(), pour obtenir l’objet. Par exemple :

listAdapter?.run {
    remove(getItem(position))
}
  1. Après avoir supprimé un élément de la liste, appelez sendRequest(request) pour actualiser la liste réorganisée et la synchroniser avec votre tableau local d’enregistrements :
    if (result.isSuccess) {
    try {
        listAdapter?.run {
            remove(getItem(position))
        }
        sendRequest("SELECT Name, Id FROM Contact")
    } catch (e: Exception) {
        onError(e)
    }
}
  1. Pour terminer, publiez une boîte d’alerte qui présente un message de réussite. Sinon, indiquez le message d'erreur.
    if (result.isSuccess) {
    try {
        listAdapter?.run {
            remove(getItem(position))
        }
        sendRequest("SELECT Name, Id FROM Contact")
        AlertDialog.Builder(this@MainActivity)
            .setMessage("Record successfully deleted!")
            .setCancelable(true)
            .create()
            .show()
    } catch (e: Exception) {
        onError(e)
    }
} else {
    Toast.makeText(this@MainActivity,
        getString(R.string.sf__generic_error),
        Toast.LENGTH_LONG,
    ).show()
}

Voici la fonction setOnItemLongClickListener() terminée.

override fun onResume() {
    // Hide everything until we are logged in
    findViewById<ViewGroup>(root).visibility = View.INVISIBLE
    // Create list adapter
    listAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, ArrayList())
    findViewById<ListView>(R.id.contacts_list).adapter = listAdapter
    val listView = findViewById<ListView>(R.id.contacts_list)
    listView.setOnItemLongClickListener { parent, view, position, id ->
        Toast.makeText(
            applicationContext,
            "Long press received",
            Toast.LENGTH_SHORT,
        ).show()
        var restRequest: RestRequest
        try {
            restRequest = RestRequest.getRequestForDelete(
                /* apiVersion = */ ApiVersionStrings.getVersionNumber(this),
                /* objectType = */ "Contact",
                /* objectId = */ records.getJSONObject(position).getString("Id"),
            )
            client!!.sendAsync(restRequest, object : AsyncRequestCallback {
                override fun onSuccess(request: RestRequest?, result: RestResponse) {
                    result.consumeQuietly()
                    runOnUiThread {
                        // The network component doesn’t report app layer status.
                        // Use Mobile SDK RestResponse.isSuccess() method to check
                        // whether the REST request itself succeeded.
                        if (result.isSuccess) {
                            try {
                                listAdapter?.run {
                                    remove(getItem(position))
                                }
                                sendRequest("SELECT Name, Id FROM Contact")
                                AlertDialog.Builder(this@MainActivity)
                                    .setMessage("Record successfully deleted!")
                                    .create()
                                    .show()
                            } catch (e: Exception) {
                                onError(e)
                            }
                        } else {
                            Toast.makeText(this@MainActivity,
                                getString(R.string.sf__generic_error),
                                Toast.LENGTH_LONG,
                            ).show()
                        }
                    }
                }
                override fun onError(exception: java.lang.Exception) {
                    runOnUiThread {
                        Toast.makeText(
                            this@MainActivity,
                            this@MainActivity.getString(R.string.sf__generic_error),
                            Toast.LENGTH_LONG
                        ).show()
                    }
                }
            })
        } catch (e: Exception) {
            e.printStackTrace()
        }
        true
    }
    super.onResume()
}

Mise à jour finale et exécution !

Le bouton Extraire les comptes est supprimé lors d'un léger nettoyage final. L'affichage de la liste étant partagé entre Extraire les contacts et Extraire les comptes, votre gestionnaire de pression longue s'applique également aux deux. Cependant, ce gestionnaire est inutile pour les comptes, car l'ID que vous utilisez pour supprimer l'enregistrement s'applique uniquement à un contact. En tant que « crédit supplémentaire », vous pouvez également appliquer ce que vous avez appris et adapter le gestionnaire de pression longue pour supprimer à la fois les contacts et les comptes. Cependant, pour ce didacticiel, supprimons le code associé aux comptes.

Supprimez les éléments suivants des fichiers indiqués.

Fichier

Action

MainActivity.kt

Supprimez la fonction onFetchAccountsClick(v: View).

res/layout/Main.xml

Effectuez l’une des actions suivantes :

  • Dans Graphical Layout (Présentation graphique), supprimez le bouton Fetch Accounts (Extraire les comptes).
  • Dans l’affichage XML, supprimez le nœud <Button> dont l’ID est « @+id/fetch_accounts ».

res/values/strings.xml

Effectuez l’une des actions suivantes :

  • Dans l’onglet Resources (Ressources) : sélectionnez "fetch_accounts_button (String)" et cliquez sur Remove (Supprimer).
  • Dans l’affichage XML : supprimez le nœud <string> qui s’intitule "fetch_accounts_button".

Enfin, votre application est terminée et prête à fonctionner !

  1. Dans Android Studio, cliquez sur Run (Exécuter) | Run ‘app’ (Exécuter l’application).
  2. Sélectionnez un appareil connecté ou un émulateur compatible avec le kit de développement Mobile SDK.
  3. Lorsque l’application est exécutée, connectez-vous à votre organisation Salesforce, puis cliquez sur Fetch Contacts pour afficher la liste. Touchez et maintenez un élément de la liste jusqu'à ce que vous voyiez un message toast qui confirme la pression longue.

Notez que vous recevez une réponse d’erreur lorsque vous essayez de supprimer un contact par défaut dans la base de données Developer Edition. Ces erreurs se produisent car chaque contact pré-empaqueté dans une organisation Developer Edition est le parent d’autres enregistrements. Pour préparer le test, connectez-vous à votre organisation Developer Edition, puis créez un ou plusieurs contacts test qui n’ont pas d’autres enregistrements. Si la suppression est réussie, un message apparaît indiquant que l'enregistrement a été supprimé.

Ressources

Partagez vos commentaires sur Trailhead dans l'aide Salesforce.

Nous aimerions connaître votre expérience avec Trailhead. Vous pouvez désormais accéder au nouveau formulaire de commentaires à tout moment depuis le site d'aide Salesforce.

En savoir plus Continuer à partager vos commentaires