Ändern der nativen Forcedroid-Anwendung
Lernziele
Nachdem Sie diese Lektion abgeschlossen haben, sind Sie in der Lage, die folgenden Aufgaben auszuführen:
- Ändern der nativen Android-Vorlagenanwendung, um die Listenanzeige anzupassen
- Verarbeiten einer REST-Antwort
- Löschen eines Salesforce-Datensatzes mittels REST-Anforderung
Anpassen der Listenanzeige
Wie bereits früher gezeigt, zeigt die native Android-Vorlagenanwendung eine Liste von Kontakten oder Accounts aus der Salesforce-Organisation des Benutzers an. Dabei handelt es sich derzeit um eine schreibgeschützte Liste, die über eine einfache SOQL SELECT-Abfrage erstellt wird, die durch die REST-Klassen des Mobile SDK verarbeitet wird. Durch geeignete Bearbeitung soll die Anwendung um eine Löschaktion für lange Fingerdrücke erweitert werden. Wenn der Benutzer längere Zeit mit dem Finger auf einen Kontaktnamen in der Listenansicht tippt, versucht die neue Löschaktion, den zugehörigen Salesforce-Datensatz zu löschen. Wenn dieser Versuch erfolgreich ist, wird die Zeile dauerhaft aus der Kontaktlistenansicht in der Anwendung entfernt. Wenn die Anforderung fehlschlägt, wird der Benutzer über die entsprechende Ursache informiert und die Zeile der Listenansicht wird wieder eingefügt.
Informationen zum LongClick-Listener
Die Implementierung des LongClick-Listeners ist unkompliziert. Die Entscheidung, wie die LongClick-Listener-Klasse erstellt wird, ist jedoch etwas kniffliger. Wenn Sie die verschiedenen Codierungsoptionen untersuchen, stellen Sie zuerst fest, dass auf der Ebene der Listenansicht keine Klicks empfangen werden, sondern auf der Ebene der Listenelemente. Glücklicherweise ist die Listener-Implementierung für Listenelemente dank der OnItemLongClickListener-Schnittstelle von Android keine beschwerliche Aufgabe. Diese Schnittstelle definiert einen einzelnen Listener, der mit der Listenansicht verknüpft ist und auf lange Fingerdrücke auf Elemente in der Liste reagiert. Sie erstellen eine Instanz dieser Klasse, indem Sie in Ihrer Ansichtsklasse eine öffentliche Schnittstelle implementieren.
Die nächste Herausforderung besteht darin, festzustellen, welche Ansichtsklasse den LongClick-Listener implementiert. Mit ListView-Objekten stellen Sie ein Datenobjekt bereit, das die in der Liste angezeigten Informationen zur Verfügung stellt. Die Vorlagenanwendung des Mobile SDK erstellt zu diesem Zweck ein ArrayAdapter<Zeichenfolge>-Objekt, das anschließend mit dem ListView-Objekt verknüpft.
listAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList<String>()); ((ListView) findViewById(R.id.contacts_list)).setAdapter(listAdapter);
ArrayAdapter<Zeichenfolge> ist aber ein Datenobjekt und keine Ansicht. Richtig? Ja und darüber hinaus noch mehr. Der ArrayAdapter der Listenansicht für Kontakte erstellt für jedes Element im Datenset der Liste ein AdapterView-Objekt. Verwenden Sie die AdapterView-Klasse zum Implementieren von OnItemLongClickListener, da diese Adapteransichten die Objekte darstellen, die Sie interessieren. Anschließend verknüpfen Sie das Listener-Objekt mit dem ListView-Objekt, das alle Benachrichtigungen für die jeweiligen untergeordneten Objekte empfängt. Diese Verknüpfung beschränkt die Interaktion von OnItemLongClickListener auf die Elemente in der Listenansicht der Vorlagenanwendung. Schließlich implementieren Sie das Löschverhalten in dieser einzigen Schnittstellenmethode.
Ein letztes Detail, das es zu lösen gilt: Wo fügen Sie diesen LongClick-Listener-Code ein? Mobile SDK bietet Ihnen die folgenden Rückmeldungsmethoden für Einstiegspunkte:
public abstract void onResume(RestClient client);
@Override protected void onCreate(Bundle savedInstanceState);
@Override public void onResume();
Davon kann eine entfernt werden: onCreate(Bundle savedInstanceState). Diese Methode konfiguriert die Aktivität und verarbeitet die Authentifizierungs-Flows, bevor die Ansichten instanziiert werden. Die Ansichten kommen erst in der onResume()-Methode zum Vorschein. Diese Methode scheint somit der wahrscheinlichste Kandidat zu sein. Die onResume(RestClient client)-Methode wird bei der Anmeldung von der Superklasse aufgerufen, um das authentifizierte RestClient-Objekt zu erfassen – dieses lassen wir allein stehen. So haben wir nun die Ergebnisse zusammen – fügen Sie Ihren LongClick-Listener-Code zu onResume() hinzu.
Implementieren eines einfachen LongClick-Listeners
- Öffnen Sie in Android Studio Ihre Datei MainActivity.java.
- Suchen Sie nach der Methode onResume().Wir beginnen Sie die Codierung wie hier gekennzeichnet – nachdem listAdapter für ListView festgelegt wurde, aber vor dem super.onResume()-Aufruf.
@Override public void onResume() { // Hide everything until we are logged in findViewById(R.id.root).setVisibility(View.INVISIBLE); // Create list adapter listAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList<String>()); ((ListView) findViewById(R.id.contacts_list)).setAdapter(listAdapter); // ADD CODE HERE! super.onResume(); }
- deklarieren Sie zur Vereinfachung eine ListView-Variable, die auf die Contacts-Listenansicht verweist, und ordnen Sie diese zu. Verwenden Sie die Activity.findViewById()-Methode, um in der Listenansichtsressource zu suchen.
((ListView) findViewById(R.id.contacts_list)).setAdapter(listAdapter); ListView lv = ((ListView) findViewById(R.id.contacts_list)); super.onResume();
- Rufen Sie mit der lv-Variablen die AdapterView.setOnItemLongClickListener()-Methode auf, um einen Listener für LongClick-Ereignisse einzurichten. Für den Listener-Parameter müssen Sie eine Inline-Vorlage der AdapterView.OnItemLongClickListener-Oberfläche instanziieren.Beachten Sie, dass Android Studio auch bei der Methode mit einzelner virtueller Oberfläche eine Vorlage für Sie erzeugt. Sehr nützlich!
ListView lv = ((ListView) findViewById(R.id.contacts_list)); lv.setOnItemLongClickListener( new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { return false; } });
- (Optional) Wenn Sie eine Fehlermeldung über einen fehlenden Import erhalten, fügen Sie import android.widget.AdapterView zu Ihren Klassenimporten hinzu.
- Innerhalb des AdapterView.OnItemLongClickListener-textkörpers müssen Sie die vorgefertigte return-Anweisung durch Code ersetzen, der eine Bestätigungsmeldung einblendet.
ListView lv = ((ListView) findViewById(R.id.contacts_list)); lv.setOnItemLongClickListener (new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(getApplicationContext(), "Long press received", Toast.LENGTH_SHORT).show(); return true; } });
- Stellen Sie die Anwendung zusammen, und führen Sie sie aus.
- Wenn Sie bei der Anwendung angemeldet sind, klicken Sie auf Fetch Contacts.
- Tippen Sie mehrere Sekunden lang auf einen Eintrag in der Liste der Kontakte. Wenn Alles in Ordnung ist, wird die Popupmeldung angezeigt.
Hinzufügen von Mobile SDK REST-Anforderungen
Wir sind schon fast startbereit, um Mobile SDK-Elemente hinzuzufügen. Um den Salesforce-Datensatz für die angetippte Zeile zu löschen, erstellen Sie in der onItemLongClick()-Methode eine REST-Anforderung. Diese Anforderung senden Sie dann an Salesforce. Bevor wir uns näher mit dem Code befassen, schauen Sie sich diese wichtigen Punkte in Ruhe an.
Abrufen einer RestClient-Instanz
Denken Sie daran: RestClient-Objekte werden niemals direkt erstellt. Das Mobile SDK erstellt eines für Sie und gibt es mithilfe der onResume(RestClient client)-Methode an MainActivity zurück. Diese RestClient-Instanz wird mit dem Zugriffs-Token des aktuellen Benutzers authentifiziert. Zu Ihren Zwecken weist die onResume(RestClient client)-Methode diese Instanz der client-Klassenvariablen zu.
Erstellen einer REST-Anforderung
Zum Erstellen der REST-Anforderung für diese Übung rufen Sie die RestRequest-Factory-Methode auf, um einen Datensatz zu löschen:
public static RestRequest getRequestForDelete(String apiVersion, String objectType, String objectId);
Woher kriegen Sie die Argumentwerte? Sehen Sie sich diese Tabelle an.
Parameter | Value (Wert) |
---|---|
apiVersion | In den Ressourcen Ihrer Anwendung definiert: getString(R.string.api_version) |
objectType | "Kontakt" (hartcodiert) |
objectId | ?? |
- Sie haben die IDs nicht, weil diese in den ursprünglichen REST-Anforderungen (den Anforderungen, die die Liste füllen) nicht angefordert wurden.
- Sie können die ID abrufen, indem Sie die REST-Anforderungen ändern.
Optimieren der SOQL-Anforderung der Vorlagenanwendung
Ihre MainActivity-Klasse gibt zwei REST-Abfragen aus: eine für Kontakte, die andere für Accounts. Jede Abfrage enthält eine SOQL-Anweisung. Wir verwenden hier keine Accounts, sondern aktualisieren die Kontaktdatensatzabfrage, um ID-Werte zurückzugeben.
- Öffnen Sie in Android Studio die Datei MainActivity.java.
- Suchen Sie nach der Methode onFetchContactsClick().
public void onFetchContactsClick(View v) throws UnsupportedEncodingException { sendRequest("SELECT Name FROM Contact"); }
- Ändern Sie die SOQL-Abfrage, indem Sie die Felder Name und Id auswählen. Achten Sie auf die Camel-Case-Schreibweise des ID-Feldnamens. Bei Feldnamen wird die Groß-/Kleinschreibung berücksichtigt.
public void onFetchContactsClick(View v) throws UnsupportedEncodingException { sendRequest("SELECT Name, Id FROM Contact"); }
Da Sie nun gewappnet sind, um die ID-Werte in der REST-Antwort zu erhalten: Wo kann man diese am besten ablegen? Die Vorlagenanwendung kopiert den Name-Wert der einzelnen Datensätze lediglich in eine Listenansichtszeile - die betreffenden Werte werden nicht zwischengespeichert. Um Suchkomponenten in Ihrem LongClick-Handler zu aktivieren, müssen Sie die IDs im Klassenbereich speichern.
Anpassen der sendRequest()-Methode der Vorlagenanwendung
RestRequest restRequest = RestRequest.getRequestForQuery( ApiVersionStrings.getVersionNumber(this), soql);
Anschließend wird das neue RestRequest-Objekt im client.sendAsync()-Aufruf an Salesforce gesendet.
client.sendAsync(restRequest, new AsyncRequestCallback() { @Override public void onSuccess(RestRequest request, final RestResponse result) { result.consumeQuietly(); // consume before going back to main thread runOnUiThread(new Runnable() { @Override public void run() { try { listAdapter.clear(); JSONArray records = result.asJSONObject().getJSONArray("records"); for (int i = 0; i < records.length(); i++) { listAdapter.add(records.getJSONObject(i).getString("Name")); } } catch (Exception e) { onError(e); } } }); } @Override public void onError(final Exception exception) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, MainActivity.this.getString( R.string.sf__generic_error, exception.toString()), Toast.LENGTH_LONG).show(); } }); } });
Zusätzlich zum RestRequest-Objekt benötigt der sendAsync()-Aufruf eine Implementierung der Mobile SDK AsyncRequestCallback-Schnittstelle. Die onSuccess()-Methode dieser Oberfläche empfängt die REST-Antwort asynchron über eine Rückmeldung. Ihre standardmäßige AsyncRequestCallback-Implementierung verarbeitet nur die SOQL-Abfrage, die in Ihrer forcedroid-Anwendung definiert werden.
- Deklarieren Sie neben dem oberen Abschnitt mit der Definition der MainActivity-Klasse JSONArray records mit den vorhandenen Klassenvariablendeklarationen als private Variable:
public class MainActivity extends SalesforceActivity { private RestClient client; private ArrayAdapter<String> listAdapter; private JSONArray records; ….
- Entfernen Sie in der onSuccess()-Methode die Typdeklaration für die records-Variable:
public void onSuccess(RestRequest request, final RestResponse result) { result.consumeQuietly(); // consume before going back to main thread runOnUiThread(new Runnable() { @Override public void run() { try { listAdapter.clear(); records = result.asJSONObject().getJSONArray("records"); for (int i = 0; i < records.length(); i++) { listAdapter.add(records.getJSONObject(i).getString("Name")); } } catch (Exception e) { onError(e); } } }); }
Fertigstellen der onItemLongClick()-Methode
Sie können jetzt die onItemLongClick()-Methode fertigstellen. Der Basisalgorithmus lautet wie folgt:
- Abrufen eines "Löschanforderungs"-Objekts durch Aufrufen einer entsprechenden Mobile SDK RestRequest-Factory-Methode. Alle RestRequest-Methoden lösen Ausnahmen aus. Sie müssen den Aufruf daher in einen try...catch-Block einschließen.
- Senden des "Löschanforderungs"-Objekts an Salesforce unter Verwendung des generierten RestClient-Objekts.
- Verarbeiten Sie das REST-Ergebnis in Rückmeldungsmethoden.
Anfordern eines RestRequest-Objekts
- Blättern Sie zurück zur onItemLongClick()-Methode in der onResume()-Methode.
- Deklarieren Sie nach dem Toast.makeText()-Aufruf ein lokales RestRequest-Objekt namens restRequest. Initialisieren Sie es mit null.
RestRequest restRequest = null;
- Fügen Sie ein leeres try...catch-Konstrukt hinzu.
RestRequest restRequest = null; try { } catch (Exception e) { }
- Rufen Sie im try-Block eine Factory-Methode auf, die ein REST-Anforderungsobjekt für einen Löschvorgang anfordert. TIPP: Verwenden Sie die statische RestRequest.getRequestForDelete()-Methode.
RestRequest restRequest = null; try { restRequest = RestRequest.getRequestForDelete( // arguments? ); } catch (Exception e) { }
- Rufen Sie für den ersten Parameter die in den Mobile SDK-Ressourcen Ihres Projekts angegebene Salesforce-API-Version ab.
RestRequest restRequest = null; try { restRequest = RestRequest.getRequestForDelete( getString(R.string.api_version), //...); } catch (Exception e) { }
- Geben Sie für den objectType-Parameter "Contact" an.
RestRequest restRequest = null; try { restRequest = RestRequest.getRequestForDelete( getString(R.string.api_version), "Contact", //...); } catch (Exception e) { }
- Übergeben Sie an RestRequest.getRequestForDelete() die ID des Eintrags im Datensatz-Array, der der aktuellen Position in der Listenansicht entspricht. So rufen Sie die ID ab:
RestRequest restRequest = null; try { restRequest = RestRequest.getRequestForDelete( getString(R.string.api_version), "Contact", records.getJSONObject(position).getString("Id")); // Send the request // ... } catch (Exception e) { }
- Rufen Sie im catch-Block printStackTrace() für das Exception-Argument auf.
RestRequest restRequest = null; try { restRequest = RestRequest.getRequestForDelete( getString(R.string.api_version), "Contact", records.getJSONObject(position).getString("Id")); // Send the request // ... } catch (Exception e) { e.printStackTrace(); }
Senden Sie das erhaltene "Löschanforderungs"-Objekt an Salesforce und verarbeiten Sie das Ergebnis in Rückmeldungsmethoden.
Hinzufügen der RestClient.sendAsync()-Methode
Sie sind fast fertig! Das letzte Puzzleteil besteht darin, die Anforderung mit der RestClient.sendAsync()-Methode zu senden. Für diese Methode müssen Sie die AsyncRequestCallback-Schnittstelle implementieren. Wie Sie wissen, sendet das Mobile SDK REST-Antworten an Ihre AsyncRequestCallback-Methoden.
- In onItemLongClick() nach dem Aufruf getRequestForDelete() kopieren Sie den Code RestClient.sendAsync() aus der sendRequest()-Methode und fügen Ihn ein.
- Entfernen Sie den internen Code der try-Verzweigung der onSuccess()-Methode. Entfernen Sie nicht die catch-Verzweigung, da lediglich ein Fehlerhandler aufgerufen wird.
- Behalten Sie die onError()-Override-Implementierung, da sie so allgemeingültig ist, dass sie für jede Salesforce-Antwort funktioniert.
Hier ist der Aufruf an RestClient.sendAsync() in Stub-Form.
restRequest = RestRequest.getRequestForDelete( getString(R.string.api_version), "Contact", records.getJSONObject(position).getString("Id")); client.sendAsync(restRequest, new AsyncRequestCallback() { @Override public void onSuccess(RestRequest request, final RestResponse result) { result.consumeQuietly(); runOnUiThread(new Runnable() { @Override public void run() { // 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 (Exception e) { onError(e); } } } }); } @Override public void onError(final Exception exception) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, MainActivity.this.getString(R.string.sf__generic_error, exception.toString()), Toast.LENGTH_LONG).show(); } }); } });
Implementieren der onSuccess()-Rückmeldungsmethode
- Vergewissern Sie sich, dass der Löschvorgang erfolgreich war, indem Sie den HTTP-Status testen. Diese Prüfung ist erforderlich, weil die zugrunde liegende Netzwerkkomponente nur Transport Layer-Fehler berichtet und keine REST-Anforderungsfehler.
- Wenn der Vorgang erfolgreich war, entfernen Sie das Element an der gegebenen Position aus der Listenansicht.
- Posten Sie eine Erfolgsmeldung.
listAdapter.remove(listAdapter.getItem(position));
- Deklarieren und initialisieren Sie am Anfang der Klasse eine private Klassenvariable namens pos vom Typ int.
public class MainActivity extends SalesforceActivity { private RestClient client; private ArrayAdapter<String> listAdapter; private JSONArray records; private int pos = -1;
- Erfassen Sie in der ersten Zeile der onItemLongClick()-Methode den position-Wert:
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { pos = position; ...
- Blättern Sie in der onSuccess()-Methode Ihrer AsyncRequestCallback-Implementierung ein paar Zeilen nach unten zum eingezogenen if-Block.
if (result.isSuccess()) { try { } catch (Exception e) { onError(e); } }
- Wenn result.isSuccess() "true" ist, entfernen Sie die Zeile, indem Sie die listAdapter.remove()-Methode aufrufen. Verwenden Sie pos statt position, um die Zeile zu entfernen:
if (result.isSuccess()) { listAdapter.remove(listAdapter.getItem(pos)); }
- Nachdem Sie ein Listenelement entfernt haben, rufen Sie sendRequest(request) auf, um die neu geordnete Liste zu aktualisieren und sie mit Ihrem lokalen records-Array synchron zu halten:
if (result.isSuccess()) { listAdapter.remove(listAdapter.getItem(pos)); sendRequest(”SELECT Name, Id FROM Contact”); }
- Erstellen Sie schließlich ein Warnfeld, in dem eine Erfolgsmeldung angezeigt wird. Zeigen Sie andernfalls die Fehlermeldung an.
if (result.isSuccess()) { listAdapter.remove(listAdapter.getItem(pos)); sendRequest(”SELECT Name, Id FROM Contact”); AlertDialog.Builder b = new AlertDialog.Builder(findViewById(R.id.contacts_list).getContext()); b.setMessage("Record successfully deleted!"); b.setCancelable(true); AlertDialog a = b.create(); a.show(); } else { Toast.makeText(MainActivity.this, MainActivity.this.getString(R.string.sf__generic_error, result.toString()), Toast.LENGTH_LONG).show(); }
Die vollständige onItemLongClick()-Methode sieht so aus.
@Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { pos = position; Toast.makeText(getApplicationContext(), "Long press detected", Toast.LENGTH_SHORT).show(); RestRequest restRequest = null; try { RestRequest request = RestRequest.getRequestForDelete( getString(R.string.api_version), "Contact", records.getJSONObject(position).getString("Id")); client.sendAsync(request, new AsyncRequestCallback() { @Override public void onSuccess(RestRequest request, final RestResponse result) { result.consumeQuietly(); runOnUiThread(new Runnable() { @Override public void run() { try { // 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()) { listAdapter.remove(listAdapter.getItem(pos)); sendRequest(”SELECT Name, Id FROM Contact”); AlertDialog.Builder b = new AlertDialog.Builder(findViewById (R.id.contacts_list).getContext()); b.setMessage("Record successfully deleted!"); b.setCancelable(true); AlertDialog a = b.create(); a.show(); } else { Toast.makeText(MainActivity.this, MainActivity.this.getString( R.string.sf__generic_error, result.toString()), Toast.LENGTH_LONG).show(); } } catch (Exception e) { onError(e); } }}); } @Override public void onError(final Exception exception) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, MainActivity.this.getString( R.string.sf__generic_error, exception.toString()), Toast.LENGTH_LONG).show(); } }); } }); } catch (Exception e) { e.printStackTrace(); } return true; }
Letzte Bereinigung ... und Action!
Eine abschließende Bereinigungsaufgabe besteht darin, die Schaltfläche Fetch Accounts zu entfernen. Da die Listenansicht von Fetch Contacts und Fetch Accounts gemeinsam genutzt wird, gilt der LongClick-Handler gleichermaßen für beide. Dieser Handler ist jedoch für Accounts nutzlos, weil die verwendete ID zum Löschen des Datensatzes nur für einen Kontakt gilt. Alternativ (als "Zusatzbonus") können Sie das Gelernte anwenden und den LongClick-Handler so anpassen, dass Kontakte und Accounts gelöscht werden. In diesem Lernprogramm entfernen wir jedoch nur den accountbezogenen Code.
Löschen Sie die folgenden Elemente auf den angegebenen Dateien:
Datei | Aktion |
---|---|
MainActivity.java | Löschen Sie die onFetchAccountsClick(View v)-Methode. |
res/layout/Main.xml | Wählen Sie eine der folgenden Vorgehensweisen aus:
|
res/values/strings.xml | Wählen Sie eine der folgenden Vorgehensweisen aus:
|
So, nun ist Ihre Anwendung fertig und kann ausgeführt werden!
- Klicken Sie in Android Studio auf .
- Wählen Sie einen Mobile SDK-kompatiblen Emulator oder ein verbundenes Gerät.
- Wenn die Anwendung ausgeführt wird, melden Sie sich bei Ihrer Salesforce-Organisation an und klicken dann auf Fetch Contacts, um die Liste anzuzeigen. Tippen Sie solange auf ein Element in der Liste, bis eine Popupmeldung den langen Fingerdruck bestätigt.
Achten Sie darauf: Für jeden Standardkontakt in der Developer Edition-Datenbank, den Sie zu löschen versuchen, wird eine Fehlerantwort angezeigt. Diese Fehler treten auf, weil jeder Kontakt, der bereits in einer Developer Edition-Organisation vordefiniert ist, das übergeordnete Element anderer Datensätze ist. Zur Vorbereitung auf die Tests müssen Sie sich bei Ihrer Developer Edition-Organisation anmelden und einen oder mehrere Kontakte erstellen, die keine anderen Datensätze besitzen. Bei einem erfolgreichen Löschvorgang wird eine Meldung mit dem Hinweis angezeigt, dass der Datensatz gelöscht wurde.