Dieses Codelab ist Teil des Kurses „Grundlagen von Android und Kotlin“. Sie können diesen Kurs am besten nutzen, wenn Sie die Codelabs der Reihe nach durcharbeiten. Alle Codelabs des Kurses sind auf der Landingpage für Codelabs zu den Grundlagen von Android und Kotlin aufgeführt.
Einführung
Fast jede Android-App, die Sie entwickeln, muss irgendwann eine Verbindung zum Internet herstellen. In diesem und den folgenden Codelabs erstellen Sie eine App, die eine Verbindung zu einem Webdienst herstellt, um Daten abzurufen und anzuzeigen.  Außerdem bauen Sie auf dem auf, was Sie in früheren Codelabs zu ViewModel, LiveData und RecyclerView gelernt haben. 
In diesem Codelab verwenden Sie von der Community entwickelte Bibliotheken, um die Netzwerkschicht zu erstellen. Dadurch wird das Abrufen der Daten und Bilder erheblich vereinfacht und die App entspricht einigen Android-Best Practices, z. B. dem Laden von Bildern in einem Hintergrundthread und dem Zwischenspeichern geladener Bilder. Für die asynchronen oder nicht blockierenden Abschnitte im Code, z. B. für die Kommunikation mit der Webdienstebene, müssen Sie die App so ändern, dass Kotlin-Coroutinen verwendet werden. Sie aktualisieren auch die Benutzeroberfläche der App, wenn das Internet langsam oder nicht verfügbar ist, um den Nutzer darüber zu informieren, was gerade passiert.
Was Sie bereits wissen sollten
- Fragmente erstellen und verwenden
 - Wie Sie zwischen Fragmenten wechseln und 
safeArgsverwenden, um Daten zwischen Fragmenten zu übergeben. - Verwendung von Architekturkomponenten wie 
ViewModel,ViewModelProvider.Factory,LiveDataundLiveData-Transformationen. - Verwendung von Coroutinen für lang andauernde Aufgaben
 
Lerninhalte
- Was ein REST-Webdienst ist.
 - Verwenden der Retrofit-Bibliothek, um eine Verbindung zu einem REST-Webdienst im Internet herzustellen und eine Antwort zu erhalten.
 - Die Moshi-Bibliothek wird verwendet, um die JSON-Antwort in ein Datenobjekt zu parsen.
 
Aufgabe
- Eine Starter-App so ändern, dass eine Webdienst-API-Anfrage gesendet und die Antwort verarbeitet wird.
 - Implementieren Sie mit der Retrofit-Bibliothek eine Netzwerkschicht für Ihre App.
 - Parsen Sie die JSON-Antwort des Webdienstes mit der Moshi-Bibliothek in die Livedaten Ihrer App.
 - Verwenden Sie die Unterstützung von Retrofit für Coroutinen, um den Code zu vereinfachen.
 
In diesem Codelab und den folgenden Codelabs arbeiten Sie mit einer Starter-App namens „MarsRealEstate“, in der zum Verkauf stehende Immobilien auf dem Mars angezeigt werden. Diese App stellt eine Verbindung zu einem Webdienst her, um die Immobiliendaten abzurufen und anzuzeigen, einschließlich Details wie Preis und ob die Immobilie zum Verkauf oder zur Vermietung verfügbar ist. Die Bilder, die die einzelnen Grundstücke darstellen, sind echte Fotos vom Mars, die von den Mars-Rovern der NASA aufgenommen wurden.

Die Version der App, die Sie in diesem Codelab erstellen, ist nicht sehr visuell. Sie konzentriert sich auf die Netzwerkschicht der App, um eine Verbindung zum Internet herzustellen und die Rohdaten der Immobilie über einen Webdienst herunterzuladen. Damit die Daten richtig abgerufen und geparst werden, geben Sie einfach die Anzahl der Immobilien auf dem Mars in einer Textansicht aus:

.
Die Architektur der MarsRealEstate-App besteht aus zwei Hauptmodulen:
- Ein Übersichtsfragment, das ein Raster mit Miniaturansichten von Immobilienbildern enthält, das mit einem 
RecyclerViewerstellt wurde. - Ein Detailansichtsfragment mit Informationen zu jeder Property.
 

Die App hat für jedes Fragment ein ViewModel. In diesem Codelab erstellen Sie eine Ebene für den Netzwerkdienst und die ViewModel kommuniziert direkt mit dieser Netzwerkschicht.  Das ist ähnlich wie in früheren Codelabs, als ViewModel mit der Datenbank Room kommunizierte.
Die Übersicht ViewModel ist für den Netzwerkaufruf zum Abrufen der Immobilieninformationen für den Mars verantwortlich. Das Detail ViewModel enthält Details für das einzelne Grundstück auf dem Mars, das im Detailfragment angezeigt wird.  Für jedes ViewModel verwenden Sie LiveData mit dem lebenszyklusbezogenen Data Binding, um die App-UI zu aktualisieren, wenn sich die Daten ändern.  
Sie verwenden die Navigationskomponente, um zwischen den beiden Fragmenten zu wechseln und die ausgewählte Property als Argument zu übergeben.
In dieser Aufgabe laden Sie die Starter-App für MarsRealEstate herunter und führen sie aus. Außerdem machen Sie sich mit der Struktur des Projekts vertraut.
Schritt 1: Fragments und Navigation kennenlernen
- Laden Sie die MarsRealEstate-Starter-App herunter und öffnen Sie sie in Android Studio.
 - Sehen Sie sich 
app/java/MainActivity.ktan. Die App verwendet Fragmente für beide Bildschirme. Die einzige Aufgabe für die Aktivität besteht also darin, das Layout der Aktivität zu laden. - Sehen Sie sich 
app/res/layout/activity_main.xmlan. Das Aktivitätslayout ist der Host für die beiden Fragmente, die in der Navigationsdatei definiert sind. In diesem Layout wird einNavHostFragmentund der zugehörige Navigationscontroller mit dernav_graph-Ressource instanziiert. - Öffnen Sie 
app/res/navigation/nav_graph.xml. Hier sehen Sie die Navigationsbeziehung zwischen den beiden Fragmenten. Der NavigationsgraphStartDestinationverweist aufoverviewFragment. Das Übersichtsfragment wird also beim Starten der App instanziiert. 
Schritt 2: Kotlin-Quelldateien und Data Binding ansehen
- Maximieren Sie im Bereich Project (Projekt) app > java. Die MarsRealEstate-App hat drei Paketordner:  
detail,networkundoverview. Sie entsprechen den drei Hauptkomponenten Ihrer App: den Übersichts- und Detailfragmenten sowie dem Code für die Netzwerkschicht.
   - Öffnen Sie 
app/java/overview/OverviewFragment.kt. DerOverviewFragmentinitialisiert denOverviewViewModelverzögert. Das bedeutet, dass derOverviewViewModelbeim ersten Aufruf erstellt wird. - Sehen Sie sich die Methode 
onCreateView()an. Mit dieser Methode wird dasfragment_overview-Layout mithilfe der Datenbindung aufgeblasen, der Lebenszyklus-Inhaber der Bindung wird auf sich selbst (this) festgelegt und die VariableviewModelimbinding-Objekt wird darauf festgelegt. Da wir den Lifecycle-Inhaber festgelegt haben, werden alleLiveData, die im Data Binding verwendet werden, automatisch auf Änderungen überwacht und die Benutzeroberfläche wird entsprechend aktualisiert. - Öffnen Sie 
app/java/overview/OverviewViewModel. Da die Antwort einLiveDataist und wir den Lebenszyklus für die Bindungsvariable festgelegt haben, werden alle Änderungen daran in der App-Benutzeroberfläche aktualisiert. - Sehen Sie sich den Block 
initan. Wenn dasViewModelerstellt wird, wird die MethodegetMarsRealEstateProperties()aufgerufen. - Sehen Sie sich die Methode 
getMarsRealEstateProperties()an. In dieser Starter-App enthält diese Methode eine Platzhalterantwort. Ziel dieses Codelabs ist es, die AntwortLiveDatainnerhalb vonViewModelmit echten Daten zu aktualisieren, die Sie aus dem Internet abrufen. - Öffnen Sie 
app/res/layout/fragment_overview.xml. Dies ist das Layout für das Übersichtsfragment, mit dem Sie in diesem Codelab arbeiten. Es enthält die Datenbindung für das ViewModel. Sie importiertOverviewViewModelund bindet dann die Antwort vonViewModelanTextView. In späteren Codelabs ersetzen Sie die Textansicht durch ein Raster mit Bildern in einemRecyclerView. - Kompilieren Sie die App und führen Sie sie aus. In der aktuellen Version der App sehen Sie nur die Startantwort: „Set the Mars API Response here!“ (Lege hier die Mars-API-Antwort fest).
 
 
Die Immobilieninformationen von Mars werden als REST-Webdienst auf einem Webserver gespeichert. Webdienste, die die REST-Architektur verwenden, werden mit Standard-Webkomponenten und ‑protokollen erstellt.
Sie stellen eine Anfrage an einen Webdienst auf standardisierte Weise über URIs. Die bekannte Web-URL ist eigentlich ein URI-Typ und beide werden in diesem Kurs synonym verwendet. In der App für diese Lektion rufen Sie beispielsweise alle Daten vom folgenden Server ab:
https://android-kotlin-fun-mars-server.appspot.com
Wenn Sie die folgende URL in Ihren Browser eingeben, erhalten Sie eine Liste aller verfügbaren Immobilien auf dem Mars.
https://android-kotlin-fun-mars-server.appspot.com/realestate
Die Antwort eines Webdienstes ist in der Regel im JSON-Format formatiert, einem Austauschformat für die Darstellung strukturierter Daten. Im nächsten Schritt erfahren Sie mehr über JSON. Kurz gesagt ist ein JSON-Objekt eine Sammlung von Schlüssel/Wert-Paaren, die manchmal auch als Wörterbuch, Hash-Map oder assoziatives Array bezeichnet werden. Eine Sammlung von JSON-Objekten ist ein JSON-Array. Dieses Array erhalten Sie als Antwort von einem Webdienst.
Damit diese Daten in die App gelangen, muss Ihre App eine Netzwerkverbindung herstellen und mit dem Server kommunizieren. Anschließend müssen die Antwortdaten in ein Format empfangen und geparst werden, das die App verwenden kann. In diesem Codelab verwenden Sie eine REST-Clientbibliothek namens Retrofit, um diese Verbindung herzustellen.
Schritt 1: Retrofit-Abhängigkeiten zu Gradle hinzufügen
- Öffnen Sie build.gradle (Module: app).
 - Fügen Sie im Abschnitt 
dependenciesdiese Zeilen für die Retrofit-Bibliotheken hinzu: 
implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"
Beachten Sie, dass die Versionsnummern separat in der Gradle-Datei des Projekts definiert sind. Die erste Abhängigkeit ist für die Retrofit 2-Bibliothek selbst und die zweite für den Retrofit-Skalarkonverter.  Mit diesem Konverter kann Retrofit das JSON-Ergebnis als String zurückgeben. Die beiden Bibliotheken arbeiten zusammen.
- Klicken Sie auf Jetzt synchronisieren, um das Projekt mit den neuen Abhängigkeiten neu zu erstellen.
 
Schritt 2: MarsApiService implementieren
Retrofit erstellt eine Netzwerk-API für die App basierend auf den Inhalten des Webdienstes. Dabei werden Daten vom Webdienst abgerufen und über eine separate Konverterbibliothek weitergeleitet, die weiß, wie die Daten decodiert und in Form von nützlichen Objekten zurückgegeben werden. Retrofit bietet integrierte Unterstützung für gängige Webdatenformate wie XML und JSON. Retrofit erstellt letztendlich den größten Teil der Netzwerkschicht für Sie, einschließlich wichtiger Details wie das Ausführen der Anfragen in Hintergrundthreads.
Die Klasse MarsApiService enthält die Netzwerkschicht für die App. Das ist die API, die Ihr ViewModel für die Kommunikation mit dem Webdienst verwendet.  In dieser Klasse implementieren Sie die Retrofit-Dienst-API.  
- Öffnen Sie 
app/java/network/MarsApiService.kt. Derzeit enthält die Datei nur eine Konstante für die Basis-URL des Webdienstes:
. 
private const val BASE_URL = 
   "https://android-kotlin-fun-mars-server.appspot.com"- Verwenden Sie direkt unter dieser Konstante einen Retrofit-Builder, um ein Retrofit-Objekt zu erstellen.  Importieren Sie 
retrofit2.Retrofitundretrofit2.converter.scalars.ScalarsConverterFactory, wenn Sie dazu aufgefordert werden. 
private val retrofit = Retrofit.Builder()
   .addConverterFactory(ScalarsConverterFactory.create())
   .baseUrl(BASE_URL)
   .build()
Retrofit benötigt mindestens zwei Dinge, um eine Webdienst-API zu erstellen: die Basis-URI für den Webdienst und eine Converter-Factory.  Der Converter teilt Retrofit mit, was mit den Daten geschehen soll, die vom Webdienst zurückgegeben werden.  In diesem Fall soll Retrofit eine JSON-Antwort vom Webdienst abrufen und als String zurückgeben.  Retrofit hat ein ScalarsConverter, das Strings und andere primitive Typen unterstützt. Sie rufen also addConverterFactory() für den Builder mit einer Instanz von ScalarsConverterFactory auf.  Rufen Sie schließlich build() auf, um das Retrofit-Objekt zu erstellen.
- Definieren Sie direkt unter dem Aufruf des Retrofit-Builders eine Schnittstelle, die definiert, wie Retrofit über HTTP-Anfragen mit dem Webserver kommuniziert.  Importieren Sie 
retrofit2.http.GETundretrofit2.Call, wenn Sie dazu aufgefordert werden. 
interface MarsApiService {
    @GET("realestate")
    fun getProperties():
            Call<String>
}Derzeit besteht das Ziel darin, den JSON-Antwortstring vom Webdienst abzurufen. Dazu benötigen Sie nur eine Methode: getProperties().  Um Retrofit mitzuteilen, was diese Methode tun soll, verwenden Sie die Annotation @GET und geben Sie den Pfad oder Endpunkt für diese Webdienstmethode an.  In diesem Fall heißt der Endpunkt realestate. Wenn die Methode getProperties() aufgerufen wird, hängt Retrofit den Endpunkt realestate an die Basis-URL an (die Sie im Retrofit-Builder definiert haben) und erstellt ein Call-Objekt. Mit diesem Call-Objekt wird die Anfrage gestartet.
- Definieren Sie unter der 
MarsApiService-Schnittstelle ein öffentliches Objekt namensMarsApi, um den Retrofit-Dienst zu initialisieren. 
object MarsApi {
    val retrofitService : MarsApiService by lazy { 
       retrofit.create(MarsApiService::class.java) }
}Mit der Retrofit-Methode create() wird der Retrofit-Dienst selbst mit der MarsApiService-Schnittstelle erstellt.  Da dieser Aufruf ressourcenintensiv ist und die App nur eine Retrofit-Dienstinstanz benötigt, machen Sie den Dienst über ein öffentliches Objekt namens MarsApi für den Rest der App verfügbar und initialisieren den Retrofit-Dienst dort verzögert. Nachdem die Einrichtung abgeschlossen ist, erhält Ihre App bei jedem Aufruf von MarsApi.retrofitService ein Singleton-Retrofit-Objekt,  das MarsApiService implementiert.
Schritt 3: Webdienst in OverviewViewModel aufrufen
- Öffnen Sie 
app/java/overview/OverviewViewModel.kt. Scrollen Sie nach unten zur MethodegetMarsRealEstateProperties(). 
private fun getMarsRealEstateProperties() {
   _response.value = "Set the Mars API Response here!"
}In dieser Methode rufen Sie den Retrofit-Dienst auf und verarbeiten den zurückgegebenen JSON-String. Derzeit gibt es nur einen Platzhalterstring für die Antwort.
- Löschen Sie die Platzhalterzeile, in der die Antwort auf „Set the Mars API Response here!“ festgelegt ist.
 - Fügen Sie in 
getMarsRealEstateProperties()den unten gezeigten Code ein. Importieren Sieretrofit2.Callbackundcom.example.android.marsrealestate.network.MarsApi, wenn Sie dazu aufgefordert werden.
Die MethodeMarsApi.retrofitService.getProperties()gibt einCall-Objekt zurück. Anschließend können Sieenqueue()für dieses Objekt aufrufen, um die Netzwerkanfrage in einem Hintergrundthread zu starten. 
MarsApi.retrofitService.getProperties().enqueue( 
   object: Callback<String> {
})- Klicken Sie auf das rot unterstrichene Wort 
object. Wählen Sie Code > Implement methods (Code > Methoden implementieren) aus. Wählen Sie aus der Liste sowohlonResponse()als auchonFailure()aus.
Android Studio fügt den Code mit TODOs in jeder Methode hinzu: 
override fun onFailure(call: Call<String>, t: Throwable) {
       TODO("not implemented") 
}
override fun onResponse(call: Call<String>, 
   response: Response<String>) {
       TODO("not implemented") 
}- Löschen Sie in 
onFailure()das TODO und legen Sie_responseauf eine Fehlermeldung fest, wie unten gezeigt._responseist einLiveData-String, der bestimmt, was in der Textansicht angezeigt wird. In jedem Status muss_responseLiveData
aktualisiert werden.DeronFailure()-Callback wird aufgerufen, wenn die Antwort des Webdienstes fehlschlägt. Legen Sie für diese Antwort den_response-Status auf"Failure: "fest, verkettet mit der Nachricht aus demThrowable-Argument. 
override fun onFailure(call: Call<String>, t: Throwable) {
   _response.value = "Failure: " + t.message
}- Löschen Sie in 
onResponse()das TODO und legen Sie_responseauf den Antworttext fest. DeronResponse()-Callback wird aufgerufen, wenn die Anfrage erfolgreich ist und der Webdienst eine Antwort zurückgibt. 
override fun onResponse(call: Call<String>, 
   response: Response<String>) {
      _response.value = response.body()
}Schritt 4: Internetberechtigung definieren
- Kompiliere und führe die MarsRealEstate-App aus.  Die App wird sofort mit einem Fehler geschlossen. 
   - Klicken Sie in Android Studio auf den Tab Logcat und notieren Sie sich den Fehler im Log. Er beginnt mit einer Zeile wie dieser:
 
Process: com.example.android.marsrealestate, PID: 10646 java.lang.SecurityException: Permission denied (missing INTERNET permission?)
Die Fehlermeldung weist darauf hin, dass Ihrer App möglicherweise die Berechtigung INTERNET fehlt. Die Verbindung zum Internet birgt Sicherheitsrisiken. Daher haben Apps standardmäßig keine Internetverbindung. Sie müssen Android explizit mitteilen, dass die App Zugriff auf das Internet benötigt.
- Öffnen Sie 
app/manifests/AndroidManifest.xml. Fügen Sie diese Zeile direkt vor dem<application>-Tag ein: 
<uses-permission android:name="android.permission.INTERNET" />- Kompilieren Sie die App und führen Sie sie noch einmal aus.  Wenn alles mit Ihrer Internetverbindung funktioniert, sehen Sie JSON-Text mit Mars Property-Daten.

 - Tippen Sie auf Ihrem Gerät oder im Emulator auf die Schaltfläche Zurück, um die App zu schließen.
 - Versetzen Sie Ihr Gerät oder Ihren Emulator in den Flugmodus und öffnen Sie die App dann über das Menü „Zuletzt verwendet“ noch einmal oder starten Sie die App in Android Studio neu.
 

- Deaktivieren Sie den Flugmodus wieder.
 
Sie erhalten jetzt eine JSON-Antwort vom Mars-Webdienst. Das ist schon mal ein guter Anfang. Sie benötigen jedoch Kotlin-Objekte und keinen großen JSON-String. Es gibt eine Bibliothek namens Moshi, die ein Android-JSON-Parser ist, der einen JSON-String in Kotlin-Objekte konvertiert. Retrofit hat einen Converter, der mit Moshi funktioniert. Das ist also eine gute Bibliothek für Ihre Zwecke.
In dieser Aufgabe verwenden Sie die Moshi-Bibliothek mit Retrofit, um die JSON-Antwort des Webdienstes in nützliche Kotlin-Objekte für Mars Property zu parsen. Sie ändern die App so, dass anstelle des Roh-JSON die Anzahl der zurückgegebenen Mars-Eigenschaften angezeigt wird.
Schritt 1: Moshi-Bibliotheksabhängigkeiten hinzufügen
- Öffnen Sie build.gradle (Module: app).
 - Fügen Sie im Bereich „dependencies“ (Abhängigkeiten) den unten gezeigten Code ein, um die Moshi-Abhängigkeiten einzuschließen. Wie bei Retrofit wird 
$version_moshiseparat in der Gradle-Datei auf Projektebene definiert. Diese Abhängigkeiten fügen Unterstützung für die Moshi-Kern-JSON-Bibliothek und für die Kotlin-Unterstützung von Moshi hinzu. 
implementation "com.squareup.moshi:moshi:$version_moshi"
implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"- Suchen Sie im 
dependencies-Block nach der Zeile für den Retrofit-Skalarkonverter: 
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"- Ändern Sie die Zeile so, dass 
converter-moshiverwendet wird: 
implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"- Klicken Sie auf Jetzt synchronisieren, um das Projekt mit den neuen Abhängigkeiten neu zu erstellen.
 
Schritt 2: MarsProperty-Datenklasse implementieren
Ein Beispiel für einen Eintrag in der JSON-Antwort, die Sie vom Webdienst erhalten, sieht so aus:
[{"price":450000,
"id":"424906",
"type":"rent",
"img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"},
...]Die oben gezeigte JSON-Antwort ist ein Array, was durch die eckigen Klammern angezeigt wird.  Das Array enthält JSON-Objekte, die von geschweiften Klammern umgeben sind.  Jedes Objekt enthält eine Reihe von Name-Wert-Paaren, die durch Doppelpunkte getrennt sind. Namen sind in Anführungszeichen eingeschlossen. Werte können Zahlen oder Strings sein. Strings werden in Anführungszeichen gesetzt. Der price für diese Property ist beispielsweise 450.000 $und der img_src ist eine URL, die den Speicherort der Bilddatei auf dem Server angibt. 
Im obigen Beispiel hat jeder Eintrag für eine Mars-Eigenschaft die folgenden JSON-Schlüssel/Wert-Paare:
price: Der Preis des Mars-Grundstücks als Zahl.id: Die ID der Property als String.type: entweder"rent"oder"buy".img_src: Die URL des Bildes als String.
Moshi parst diese JSON-Daten und konvertiert sie in Kotlin-Objekte. Dazu ist eine Kotlin-Datenklasse zum Speichern der geparsten Ergebnisse erforderlich. Erstellen Sie diese Klasse als Nächstes.
- Öffnen Sie 
app/java/network/MarsProperty.kt. - Ersetzen Sie die vorhandene 
MarsProperty-Klassendefinition durch den folgenden Code: 
data class MarsProperty(
   val id: String, val img_src: String,
   val type: String,
   val price: Double
)Jede der Variablen in der Klasse MarsProperty entspricht einem Schlüsselnamen im JSON-Objekt.  Um die Typen im JSON abzugleichen, verwenden Sie String-Objekte für alle Werte außer price, das ein Double ist.  Mit einem Double kann jede JSON-Zahl dargestellt werden.
Wenn Moshi das JSON-Dokument parst, werden die Schlüssel anhand des Namens zugeordnet und die Datenobjekte mit entsprechenden Werten gefüllt.
- Ersetzen Sie die Zeile für den 
img_src-Schlüssel durch die unten gezeigte Zeile. Importieren Siecom.squareup.moshi.Json, wenn Sie dazu aufgefordert werden. 
@Json(name = "img_src") val imgSrcUrl: String,Manchmal können die Schlüsselnamen in einer JSON-Antwort zu verwirrenden Kotlin-Eigenschaften führen oder nicht Ihrem Programmierstil entsprechen. In der JSON-Datei wird beispielsweise für den Schlüssel img_src ein Unterstrich verwendet, während für Kotlin-Eigenschaften in der Regel Groß- und Kleinbuchstaben („Camel Case“) verwendet werden. 
Wenn Sie in Ihrer Datenklasse Variablennamen verwenden möchten, die sich von den Schlüsselnamen in der JSON-Antwort unterscheiden, verwenden Sie die Annotation @Json. In diesem Beispiel lautet der Name der Variablen in der Datenklasse imgSrcUrl. Die Variable wird mit @Json(name = "img_src") dem JSON-Attribut img_src zugeordnet.
Schritt 3: MarsApiService und OverviewViewModel aktualisieren
Nachdem Sie die Datenklasse MarsProperty eingerichtet haben, können Sie die Netzwerk-API und ViewModel aktualisieren, um die Moshi-Daten einzubeziehen.  
- Öffnen Sie 
network/MarsApiService.kt. Möglicherweise werden Fehler vom Typ „missing-class“ fürScalarsConverterFactoryangezeigt. Das liegt an der Änderung der Retrofit-Abhängigkeit, die Sie in Schritt 1 vorgenommen haben. Beheben Sie diese Fehler so bald wie möglich. - Fügen Sie oben in der Datei, direkt vor dem Retrofit-Builder, den folgenden Code hinzu, um die Moshi-Instanz zu erstellen.  Importieren Sie 
com.squareup.moshi.Moshiundcom.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory, wenn Sie dazu aufgefordert werden. 
private val moshi = Moshi.Builder()
   .add(KotlinJsonAdapterFactory())
   .build()Ähnlich wie bei Retrofit erstellen Sie hier ein moshi-Objekt mit dem Moshi-Builder.  Damit die Annotationen von Moshi in Kotlin richtig funktionieren, fügen Sie KotlinJsonAdapterFactory hinzu und rufen Sie dann build() auf.  
- Ändern Sie den Retrofit-Builder so, dass er 
MoshiConverterFactoryanstelle vonScalarConverterFactoryverwendet, und übergeben Sie diemoshi-Instanz, die Sie gerade erstellt haben. Importieren Sieretrofit2.converter.moshi.MoshiConverterFactory, wenn Sie dazu aufgefordert werden. 
private val retrofit = Retrofit.Builder()
   .addConverterFactory(MoshiConverterFactory.create(moshi))
   .baseUrl(BASE_URL)
   .build()- Löschen Sie auch den Import für 
ScalarConverterFactory. 
Zu löschender Code:
import retrofit2.converter.scalars.ScalarsConverterFactory- Aktualisieren Sie die 
MarsApiService-Schnittstelle so, dass Retrofit eine Liste vonMarsProperty-Objekten zurückgibt, anstattCall<String>.
zurückzugeben. 
interface MarsApiService {
   @GET("realestate")
   fun getProperties():
      Call<List<MarsProperty>>
}- Öffnen Sie 
OverviewViewModel.kt. Scrollen Sie in der MethodegetMarsRealEstateProperties()nach unten zum Aufruf vongetProperties().enqueue(). - Ändern Sie das Argument für 
enqueue()vonCallback<String>inCallback<List<MarsProperty>>. Importieren Siecom.example.android.marsrealestate.network.MarsProperty, wenn Sie dazu aufgefordert werden. 
MarsApi.retrofitService.getProperties().enqueue( 
   object: Callback<List<MarsProperty>> {- Ändern Sie in 
onFailure()das Argument vonCall<String>inCall<List<MarsProperty>>: 
override fun onFailure(call: Call<List<MarsProperty>>, t: Throwable) {- Nehmen Sie dieselbe Änderung an beiden Argumenten für 
onResponse()vor: 
override fun onResponse(call: Call<List<MarsProperty>>, 
   response: Response<List<MarsProperty>>) {- Ersetzen Sie im Text von 
onResponse()die vorhandene Zuweisung zu_response.valuedurch die unten gezeigte Zuweisung. Daresponse.body()jetzt eine Liste vonMarsProperty-Objekten ist, entspricht die Größe dieser Liste der Anzahl der geparsten Eigenschaften. In dieser Antwortnachricht wird die Anzahl der Properties ausgegeben: 
_response.value = 
   "Success: ${response.body()?.size} Mars properties retrieved"- Achten Sie darauf, dass der Flugmodus deaktiviert ist.  Kompilieren und führen Sie die App aus. Dieses Mal sollte in der Meldung die Anzahl der vom Webdienst zurückgegebenen Attribute angezeigt werden:

 
Der Retrofit-API-Dienst wird jetzt ausgeführt, verwendet aber einen Callback mit zwei Callback-Methoden, die Sie implementieren mussten. Eine Methode verarbeitet den Erfolg und eine andere den Fehler. Das Fehlerergebnis meldet Ausnahmen. Ihr Code wäre effizienter und leichter lesbar, wenn Sie anstelle von Callbacks Coroutinen mit Ausnahmebehandlung verwenden könnten. Praktischerweise gibt es für Retrofit eine Bibliothek, die Coroutinen integriert.
In dieser Aufgabe konvertieren Sie Ihren Netzwerkdienst und ViewModel, sodass sie Coroutinen verwenden.  
Schritt 1: Coroutine-Abhängigkeiten hinzufügen
- Öffnen Sie build.gradle (Module: app).
 - Fügen Sie im Abschnitt „dependencies“ Unterstützung für die Kotlin-Core-Coroutine-Bibliotheken und die Retrofit-Coroutine-Bibliothek hinzu:
 
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version_kotlin_coroutines" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version_kotlin_coroutines" implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$version_retrofit_coroutines_adapter"
- Klicken Sie auf Jetzt synchronisieren, um das Projekt mit den neuen Abhängigkeiten neu zu erstellen.
 
Schritt 2: MarsApiService und OverviewViewModel aktualisieren
- Aktualisieren Sie in 
MarsApiService.ktden Retrofit-Builder, damit erCoroutineCallAdapterFactoryverwendet. Der vollständige Builder sieht jetzt so aus: 
private val retrofit = Retrofit.Builder()
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .addCallAdapterFactory(CoroutineCallAdapterFactory())
        .baseUrl(BASE_URL)
        .build()Mit Call-Adaptern kann Retrofit APIs erstellen, die etwas anderes als die Standardklasse Call zurückgeben.  In diesem Fall können wir mit CoroutineCallAdapterFactory das Call-Objekt, das von getProperties() zurückgegeben wird, durch ein Deferred-Objekt ersetzen.
- Ändern Sie in der Methode 
getProperties()Call<List<MarsProperty>>inDeferred<List<MarsProperty>>. Importieren Siekotlinx.coroutines.Deferred, wenn Sie dazu aufgefordert werden. Die vollständigegetProperties()-Methode sieht so aus: 
@GET("realestate")
fun getProperties():
   Deferred<List<MarsProperty>>Die Deferred-Schnittstelle definiert einen Coroutine-Job, der einen Ergebniswert zurückgibt (Deferred erbt von Job). Die Deferred-Schnittstelle enthält eine Methode namens await(), die bewirkt, dass Ihr Code wartet, ohne zu blockieren, bis der Wert bereit ist. Anschließend wird dieser Wert zurückgegeben. 
- Öffnen Sie 
OverviewViewModel.kt. Fügen Sie direkt vor deminit-Block einen Coroutine-Job hinzu: 
private var viewModelJob = Job()- Erstellen Sie mit dem Haupt-Dispatcher einen Coroutine-Scope für diesen neuen Job:
 
private val coroutineScope = CoroutineScope(
   viewModelJob + Dispatchers.Main )Der Dispatchers.Main-Dispatcher verwendet den UI-Thread für seine Arbeit.  Da Retrofit alle Aufgaben in einem Hintergrundthread ausführt, gibt es keinen Grund, einen anderen Thread für den Bereich zu verwenden. So können Sie den Wert von MutableLiveData ganz einfach aktualisieren, wenn Sie ein Ergebnis erhalten.
- Löschen Sie den gesamten Code in 
getMarsRealEstateProperties(). Hier verwenden Sie Coroutinen anstelle des Aufrufs vonenqueue()und der CallbacksonFailure()undonResponse(). - Starten Sie die Coroutine in 
getMarsRealEstateProperties(): 
coroutineScope.launch { 
}
Damit Sie das Deferred-Objekt verwenden können, das Retrofit für die Netzwerkaufgabe zurückgibt, müssen Sie sich in einer Coroutine befinden. Daher starten Sie hier die gerade erstellte Coroutine.  Sie führen weiterhin Code im Hauptthread aus, lassen die Nebenläufigkeit aber von Coroutinen verwalten.
- Rufen Sie im Startblock 
getProperties()für dasretrofitService-Objekt auf: 
var getPropertiesDeferred = MarsApi.retrofitService.getProperties()Wenn Sie getProperties() über den MarsApi-Dienst aufrufen, wird der Netzwerkaufruf in einem Hintergrundthread erstellt und gestartet. Das Deferred-Objekt für diese Aufgabe wird zurückgegeben. 
- Fügen Sie außerdem im Startblock einen 
try-/catch-Block hinzu, um Ausnahmen zu verarbeiten: 
try {
} catch (e: Exception) {
  
}- Rufen Sie im Block 
try {}die Funktionawait()für das ObjektDeferredauf: 
var listResult = getPropertiesDeferred.await()Wenn Sie await() für das Deferred-Objekt aufrufen, wird das Ergebnis des Netzwerkaufrufs zurückgegeben, sobald der Wert bereit ist. Die Methode await() ist nicht blockierend. Der Mars API-Dienst ruft die Daten also aus dem Netzwerk ab, ohne den aktuellen Thread zu blockieren. Das ist wichtig, da wir uns im Bereich des UI-Threads befinden. Sobald die Aufgabe abgeschlossen ist, wird Ihr Code an der Stelle fortgesetzt, an der er unterbrochen wurde.  Das geschieht innerhalb des try {}, sodass Sie Ausnahmen abfangen können.  
- Aktualisieren Sie auch im 
try {}-Block nach derawait()-Methode die Antwortnachricht für die erfolgreiche Antwort: 
_response.value = 
   "Success: ${listResult.size} Mars properties retrieved"- Verarbeiten Sie im Block 
catch {}die Fehlerantwort: 
_response.value = "Failure: ${e.message}"
Die vollständige getMarsRealEstateProperties()-Methode sieht jetzt so aus:
private fun getMarsRealEstateProperties() {
   coroutineScope.launch {
       var getPropertiesDeferred = 
          MarsApi.retrofitService.getProperties()
       try {          
           _response.value = 
              "Success: ${listResult.size} Mars properties retrieved"
       } catch (e: Exception) {
           _response.value = "Failure: ${e.message}"
       }
   }
}- Fügen Sie unten in der Klasse einen 
onCleared()-Callback mit diesem Code hinzu: 
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}Das Laden von Daten sollte beendet werden, wenn das ViewModel zerstört wird, da das OverviewFragment, das dieses ViewModel verwendet, dann nicht mehr vorhanden ist. Wenn das Laden beendet werden soll, wenn ViewModel zerstört wird, überschreiben Sie onCleared(), um den Job abzubrechen.
- Kompilieren und führen Sie die App aus. Sie erhalten dasselbe Ergebnis wie in der vorherigen Aufgabe (einen Bericht über die Anzahl der Properties), aber mit einfacherem Code und einer besseren Fehlerbehandlung.
 
Android Studio-Projekt: MarsRealEstateNetwork
REST-Webdienste
- Ein Webdienst ist ein Dienst im Internet, über den Ihre App Anfragen stellen und Daten zurückerhalten kann.
 - Gängige Webservices verwenden eine REST-Architektur. Webdienste, die eine REST-Architektur bieten, werden als RESTful-Dienste bezeichnet. RESTful-Webdienste werden mit Standard-Webkomponenten und ‑Protokollen erstellt.
 - Sie stellen Anfragen an einen REST-Webdienst auf standardisierte Weise über URIs.
 - Damit eine App einen Webdienst nutzen kann, muss sie eine Netzwerkverbindung herstellen und mit dem Dienst kommunizieren. Anschließend muss die App die Antwortdaten empfangen und in ein Format parsen, das die App verwenden kann.
 - Die Retrofit-Bibliothek ist eine Clientbibliothek, mit der Ihre App Anfragen an einen REST-Webdienst senden kann.
 - Mit Konvertern können Sie Retrofit mitteilen, was mit Daten geschehen soll, die an den Webdienst gesendet und vom Webdienst zurückgegeben werden.  Der 
ScalarsConverter-Konverter behandelt die Webdienstdaten beispielsweise alsStringoder ein anderes Primitiv. - Damit Ihre App Verbindungen zum Internet herstellen kann, fügen Sie die Berechtigung 
"android.permission.INTERNET"im Android-Manifest hinzu. 
JSON-Parsing
- Die Antwort eines Webdienstes ist oft im JSON-Format formatiert, einem gängigen Austauschformat für die Darstellung strukturierter Daten.
 - Ein JSON-Objekt ist eine Sammlung von Schlüssel/Wert-Paaren. Diese Sammlung wird manchmal auch als Dictionary, Hash Map oder assoziatives Array bezeichnet.
 - Eine Sammlung von JSON-Objekten ist ein JSON-Array. Sie erhalten ein JSON-Array als Antwort von einem Webdienst.
 - Die Schlüssel in einem Schlüssel/Wert-Paar sind in Anführungszeichen eingeschlossen. Die Werte können Zahlen oder Strings sein. Strings werden ebenfalls in Anführungszeichen gesetzt.
 - Die Moshi-Bibliothek ist ein Android-JSON-Parser, der einen JSON-String in Kotlin-Objekte konvertiert. Retrofit hat einen Converter, der mit Moshi funktioniert.
 - Moshi vergleicht die Schlüssel in einer JSON-Antwort mit Attributen in einem Datenobjekt, die denselben Namen haben.
 - Wenn Sie für einen Schlüssel einen anderen Attributnamen verwenden möchten, versehen Sie das Attribut mit der Annotation 
@Jsonund dem JSON-Schlüsselnamen. 
Retrofit und Coroutinen
- Mit Call-Adaptern kann Retrofit APIs erstellen, die etwas anderes als die Standardklasse 
Callzurückgeben. Verwenden Sie die KlasseCoroutineCallAdapterFactory, umCalldurch eine CoroutineDeferredzu ersetzen. - Verwenden Sie die Methode 
await()für das ObjektDeferred, damit Ihr Coroutinen-Code wartet, ohne zu blockieren, bis der Wert bereit ist. Anschließend wird der Wert zurückgegeben. 
Udacity-Kurs:
Android-Entwicklerdokumentation:
Kotlin-Dokumentation:
Sonstiges:
In diesem Abschnitt werden mögliche Hausaufgaben für Schüler und Studenten aufgeführt, die dieses Codelab im Rahmen eines von einem Kursleiter geleiteten Kurses durcharbeiten. Es liegt in der Verantwortung des Kursleiters, Folgendes zu tun:
- Weisen Sie bei Bedarf Aufgaben zu.
 - Teilen Sie den Schülern/Studenten mit, wie sie Hausaufgaben abgeben können.
 - Benoten Sie die Hausaufgaben.
 
Lehrkräfte können diese Vorschläge nach Belieben nutzen und auch andere Hausaufgaben zuweisen, die sie für angemessen halten.
Wenn Sie dieses Codelab selbst durcharbeiten, können Sie mit diesen Hausaufgaben Ihr Wissen testen.
Beantworten Sie diese Fragen
Frage 1
Was sind die beiden wichtigsten Dinge, die Retrofit zum Erstellen einer Webdienst-API benötigt?
▢ Der Basis-URI für den Webdienst und eine GET-Abfrage.
▢ Die Basis-URI für den Webdienst und eine Converter-Factory.
▢ Eine Netzwerkverbindung zum Webdienst und ein Autorisierungstoken.
▢ Eine Converter-Factory und ein Parser für die Antwort.
Frage 2
Was ist der Zweck der Moshi-Bibliothek?
▢ Daten von einem Webdienst abrufen.
▢ Um mit Retrofit zu interagieren und eine Webdienstanfrage zu stellen.
▢ Eine JSON-Antwort von einem Webdienst in Kotlin-Datenobjekte parsen.
▢ Kotlin-Objekte umbenennen, damit sie mit den Schlüsseln in der JSON-Antwort übereinstimmen.
Frage 3
Wofür werden Retrofit-Call-Adapter verwendet?
▢ Sie ermöglichen die Verwendung von Koroutinen in Retrofit.
▢ Sie passen die Webdienstantwort an Kotlin-Datenobjekte an.
▢ Sie ändern einen Retrofit-Aufruf in einen Webdienstaufruf.
▢ Sie ermöglichen es, in Retrofit etwas anderes als die Standardklasse Call zurückzugeben.
Nächste Lektion starten: 
Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage für Android Kotlin Fundamentals-Codelabs.