Android Kotlin Fundamentals 08.1: Pobieranie danych z internetu

Ten moduł Codelab jest częścią kursu Android Kotlin Fundamentals. Najwięcej korzyści przyniesie Ci ukończenie wszystkich ćwiczeń w kolejności. Wszystkie ćwiczenia z tego kursu znajdziesz na stronie docelowej kursu Android Kotlin Fundamentals.

Wprowadzenie

Prawie każda aplikacja na Androida, którą tworzysz, będzie w pewnym momencie wymagać połączenia z internetem. W tym i kolejnych modułach utworzysz aplikację, która łączy się z usługą internetową, aby pobierać i wyświetlać dane. Wykorzystasz też wiedzę zdobytą w ramach poprzednich ćwiczeń z programowania dotyczących ViewModel, LiveDataRecyclerView.

W tym ćwiczeniu w Codelabs do utworzenia warstwy sieciowej użyjesz bibliotek opracowanych przez społeczność. Znacznie upraszcza to pobieranie danych i obrazów, a także pomaga aplikacji spełniać niektóre sprawdzone metody Androida, takie jak wczytywanie obrazów w wątku w tle i zapisywanie wczytanych obrazów w pamięci podręcznej. W przypadku asynchronicznych lub nieblokujących sekcji kodu, takich jak komunikacja z warstwą usług internetowych, zmodyfikujesz aplikację, aby używała korutyn Kotlin. Zaktualizujesz też interfejs aplikacji, jeśli internet będzie działać wolno lub będzie niedostępny, aby poinformować użytkownika o tym, co się dzieje.

Co warto wiedzieć

  • Jak tworzyć i używać fragmentów.
  • Jak przechodzić między fragmentami i używać safeArgs do przekazywania danych między nimi.
  • Jak używać komponentów architektury, w tym transformacji ViewModel, ViewModelProvider.Factory, LiveDataLiveData.
  • Jak używać korutyn do długotrwałych zadań.

Czego się nauczysz

  • Co to jest usługa internetowa REST.
  • Korzystanie z biblioteki Retrofit do łączenia się z usługą internetową REST w internecie i uzyskiwania odpowiedzi.
  • Używanie biblioteki Moshi do analizowania odpowiedzi JSON i przekształcania jej w obiekt danych.

Co musisz zrobić

  • Zmodyfikuj aplikację startową, aby wysyłać żądania do interfejsu API usługi internetowej i obsługiwać odpowiedzi.
  • Zaimplementuj w aplikacji warstwę sieciową za pomocą biblioteki Retrofit.
  • Przeanalizuj odpowiedź JSON z usługi internetowej i przekształć ją w dane na żywo w aplikacji za pomocą biblioteki Moshi.
  • Użyj obsługi korutyn w Retrofit, aby uprościć kod.

W tym samouczku (i w kolejnych) będziesz pracować z aplikacją startową MarsRealEstate, która wyświetla nieruchomości na sprzedaż na Marsie. Ta aplikacja łączy się z usługą internetową, aby pobierać i wyświetlać dane nieruchomości, w tym szczegóły takie jak cena oraz informacja, czy nieruchomość jest dostępna na sprzedaż lub wynajem. Obrazy przedstawiające poszczególne nieruchomości to prawdziwe zdjęcia z Marsa wykonane przez łaziki NASA.

Wersja aplikacji, którą utworzysz w tym laboratorium, nie będzie miała wielu elementów wizualnych. Skupia się ona na warstwie sieciowej aplikacji, która łączy się z internetem i pobiera surowe dane o nieruchomościach za pomocą usługi internetowej. Aby mieć pewność, że dane są prawidłowo pobierane i parsowane, wystarczy wydrukować liczbę obiektów na Marsie w widoku tekstowym:

.

Architektura aplikacji MarsRealEstate ma 2 główne moduły:

  • Fragment z omówieniem, który zawiera siatkę miniatur zdjęć nieruchomości utworzoną za pomocą elementu RecyclerView.
  • Fragment widoku szczegółowego zawierający informacje o każdej usłudze.

Aplikacja ma ViewModel dla każdego fragmentu. W tym laboratorium kodowania utworzysz warstwę dla usługi sieciowej, z którą ViewModel będzie się komunikować bezpośrednio. Jest to podobne do tego, co robiliśmy w poprzednich ćwiczeniach, gdy ViewModel komunikował się z bazą danych Room.

Widok ogólny ViewModel odpowiada za wysłanie wywołania sieciowego w celu uzyskania informacji o nieruchomościach na Marsie. Szczegóły ViewModel zawierają informacje o jednej nieruchomości na Marsie, która jest wyświetlana we fragmencie szczegółów. W przypadku każdego ViewModel używasz LiveData z powiązywaniem danych uwzględniającym cykl życia, aby aktualizować interfejs aplikacji, gdy dane się zmieniają.

Komponentu Navigation używasz zarówno do przechodzenia między 2 fragmentami, jak i do przekazywania wybranej usługi jako argumentu.

W tym zadaniu pobierzesz i uruchomisz aplikację początkową MarsRealEstate oraz zapoznasz się ze strukturą projektu.

Krok 1. Poznaj fragmenty i nawigację

  1. Pobierz aplikację startową MarsRealEstate i otwórz ją w Android Studio.
  2. Sprawdź app/java/MainActivity.kt. Aplikacja używa fragmentów na obu ekranach, więc jedynym zadaniem aktywności jest wczytanie układu aktywności.
  3. Sprawdź app/res/layout/activity_main.xml. Układ aktywności jest hostem dla 2 fragmentów zdefiniowanych w pliku nawigacji. Ten układ tworzy instancję elementu NavHostFragment i powiązanego z nim kontrolera nawigacji z zasobem nav_graph.
  4. Otwórz pokój app/res/navigation/nav_graph.xml. W tym miejscu możesz zobaczyć relację nawigacyjną między tymi 2 fragmentami. Wykres nawigacji StartDestination wskazuje overviewFragment, więc fragment przeglądu jest tworzony podczas uruchamiania aplikacji.

Krok 2. Zapoznaj się z plikami źródłowymi Kotlin i powiązaniami danych

  1. W panelu Project (Projekt) rozwiń app > java. Zwróć uwagę, że aplikacja MarsRealEstate ma 3 foldery pakietów: detail, networkoverview. Odpowiadają one 3 głównym komponentom aplikacji: fragmentom przeglądu i szczegółów oraz kodowi warstwy sieciowej.
  2. Otwórz pokój app/java/overview/OverviewFragment.kt. OverviewFragment leniwie inicjuje OverviewViewModel, co oznacza, że OverviewViewModel jest tworzony przy pierwszym użyciu.
  3. Sprawdź metodę onCreateView(). Ta metoda powiększa fragment_overview układ za pomocą powiązania danych, ustawia właściciela cyklu życia powiązania na samą siebie (this) i ustawia zmienną viewModel w obiekcie binding na samą siebie. Ponieważ ustawiliśmy właściciela cyklu życia, wszystkie obiekty LiveData używane w powiązaniu danych będą automatycznie obserwowane pod kątem zmian, a interfejs zostanie odpowiednio zaktualizowany.
  4. Otwórz pokój app/java/overview/OverviewViewModel. Odpowiedź jest typu LiveData, a my ustawiliśmy cykl życia zmiennej powiązania, więc wszelkie zmiany w niej będą aktualizować interfejs aplikacji.
  5. Sprawdź blok init. Po utworzeniu obiektu ViewModel wywoływana jest metoda getMarsRealEstateProperties().
  6. Sprawdź metodę getMarsRealEstateProperties(). W tej aplikacji startowej ta metoda zawiera odpowiedź zastępczą. Celem tego laboratorium jest zaktualizowanie odpowiedzi LiveDataViewModel za pomocą rzeczywistych danych pobranych z internetu.
  7. Otwórz pokój app/res/layout/fragment_overview.xml. Jest to układ fragmentu przeglądu, z którym będziesz pracować w tym laboratorium, i zawiera on powiązanie danych z modelem widoku. Importuje OverviewViewModel, a następnie wiąże odpowiedź z ViewModelTextView. W dalszych ćwiczeniach zastąpisz widok tekstu siatką obrazów w RecyclerView.
  8. Skompiluj i uruchom aplikację. W obecnej wersji aplikacji zobaczysz tylko odpowiedź początkową – „Set the Mars API Response here!” (Ustaw tutaj odpowiedź interfejsu Mars API!).

Dane dotyczące nieruchomości na Marsie są przechowywane na serwerze WWW jako usługa internetowa REST. Usługi internetowe korzystające z architektury REST są tworzone przy użyciu standardowych komponentów i protokołów internetowych.

Żądanie do usługi internetowej przesyłasz w standardowy sposób za pomocą identyfikatorów URI. Znany adres URL to w rzeczywistości typ identyfikatora URI, a w tym kursie oba te terminy są używane zamiennie. Na przykład w aplikacji z tej lekcji pobierasz wszystkie dane z tego serwera:

https://android-kotlin-fun-mars-server.appspot.com

Jeśli wpiszesz w przeglądarce ten adres URL, otrzymasz listę wszystkich dostępnych nieruchomości na Marsie.

https://android-kotlin-fun-mars-server.appspot.com/realestate

Odpowiedź usługi internetowej jest zwykle formatowana w JSON, czyli formacie wymiany danych do przedstawiania danych strukturalnych. Więcej informacji o JSON znajdziesz w następnym zadaniu. W skrócie: obiekt JSON to zbiór par klucz-wartość, czasami nazywany słownikiem, mapą mieszającą lub tablicą asocjacyjną. Zbiór obiektów JSON to tablica JSON, która jest zwracana jako odpowiedź z usługi internetowej.

Aby uzyskać te dane w aplikacji, musi ona nawiązać połączenie sieciowe i komunikować się z tym serwerem, a następnie odbierać i parsować dane odpowiedzi do formatu, którego może używać. W tym laboratorium kodowym użyjesz biblioteki klienta REST o nazwie Retrofit, aby nawiązać to połączenie.

Krok 1. Dodaj zależności Retrofit do Gradle

  1. Otwórz plik build.gradle (Module: app).
  2. W sekcji dependencies dodaj te wiersze dla bibliotek Retrofit:
implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"


Zwróć uwagę, że numery wersji są zdefiniowane osobno w pliku Gradle projektu. Pierwsza zależność dotyczy samej biblioteki Retrofit 2, a druga – konwertera skalarnego Retrofit. Ten konwerter umożliwia Retrofit zwracanie wyniku JSON jako String. Obie biblioteki współpracują ze sobą.

  1. Kliknij Synchronizuj teraz, aby ponownie utworzyć projekt z nowymi zależnościami.

Krok 2. Zaimplementuj MarsApiService

Retrofit tworzy interfejs API sieci dla aplikacji na podstawie treści z usługi internetowej. Pobiera dane z usługi internetowej i przekazuje je przez osobną bibliotekę konwertera, która wie, jak dekodować dane i zwracać je w postaci przydatnych obiektów. Retrofit ma wbudowaną obsługę popularnych formatów danych internetowych, takich jak XML i JSON. Retrofit tworzy większość warstwy sieciowej, w tym kluczowe szczegóły, takie jak uruchamianie żądań w wątkach w tle.

Klasa MarsApiService zawiera warstwę sieciową aplikacji, czyli interfejs API, którego ViewModel będzie używać do komunikacji z usługą internetową. W tej klasie zaimplementujesz interfejs API usługi Retrofit.

  1. Otwórz pokój app/java/network/MarsApiService.kt. Obecnie plik zawiera tylko jedną rzecz: stałą dla podstawowego adresu URL usługi sieciowej.
private const val BASE_URL = 
   "https://android-kotlin-fun-mars-server.appspot.com"
  1. Tuż pod tą stałą użyj kreatora Retrofit, aby utworzyć obiekt Retrofit. W razie potrzeby zaimportuj retrofit2.Retrofitretrofit2.converter.scalars.ScalarsConverterFactory.
private val retrofit = Retrofit.Builder()
   .addConverterFactory(ScalarsConverterFactory.create())
   .baseUrl(BASE_URL)
   .build()

Aby utworzyć interfejs API usług internetowych, Retrofit potrzebuje co najmniej 2 rzeczy: podstawowego identyfikatora URI usługi internetowej i fabryki konwerterów. Konwerter informuje bibliotekę Retrofit, co ma zrobić z danymi otrzymanymi z usługi internetowej. W tym przypadku chcesz, aby Retrofit pobrał odpowiedź JSON z usługi internetowej i zwrócił ją jako String. Retrofit ma ScalarsConverter, który obsługuje ciągi znaków i inne typy proste, więc w przypadku konstruktora wywołujesz addConverterFactory() z instancją ScalarsConverterFactory. Na koniec wywołaj build(), aby utworzyć obiekt Retrofit.

  1. Tuż pod wywołaniem konstruktora Retrofit zdefiniuj interfejs, który określa, w jaki sposób Retrofit komunikuje się z serwerem internetowym za pomocą żądań HTTP. Gdy pojawi się prośba, zaimportuj retrofit2.http.GETretrofit2.Call.
interface MarsApiService {
    @GET("realestate")
    fun getProperties():
            Call<String>
}

Obecnie celem jest uzyskanie ciągu znaków odpowiedzi JSON z usługi internetowej. Wystarczy do tego jedna metoda: getProperties(). Aby poinformować bibliotekę Retrofit, co ma robić ta metoda, użyj adnotacji @GET i określ ścieżkę lub punkt końcowy tej metody usługi internetowej. W tym przypadku punkt końcowy nazywa się realestate. Gdy wywoływana jest metoda getProperties(), Retrofit dołącza punkt końcowy realestate do adresu podstawowego (zdefiniowanego w konstruktorze Retrofit) i tworzy obiekt Call. Ten obiekt Call służy do rozpoczęcia żądania.

  1. Pod interfejsem MarsApiService zdefiniuj publiczny obiekt o nazwie MarsApi, aby zainicjować usługę Retrofit.
object MarsApi {
    val retrofitService : MarsApiService by lazy { 
       retrofit.create(MarsApiService::class.java) }
}

Metoda Retrofit create() tworzy samą usługę Retrofit z interfejsem MarsApiService. To wywołanie jest kosztowne, a aplikacja potrzebuje tylko jednej instancji usługi Retrofit, dlatego udostępniasz usługę reszcie aplikacji za pomocą publicznego obiektu o nazwie MarsApi i tam leniwie inicjujesz usługę Retrofit. Po zakończeniu konfiguracji za każdym razem, gdy aplikacja wywoła MarsApi.retrofitService, otrzyma obiekt Retrofit singleton, który implementuje MarsApiService.

Krok 3. Wywołaj usługę internetową w klasie OverviewViewModel

  1. Otwórz pokój app/java/overview/OverviewViewModel.kt. Przewiń w dół do metody getMarsRealEstateProperties().
private fun getMarsRealEstateProperties() {
   _response.value = "Set the Mars API Response here!"
}

To metoda, w której wywołasz usługę Retrofit i obsłużysz zwrócony ciąg JSON. Obecnie w odpowiedzi znajduje się tylko ciąg zastępczy.

  1. Usuń wiersz z tekstem zastępczym, który ustawia odpowiedź na „Set the Mars API Response here!” (Ustaw tutaj odpowiedź interfejsu Mars API).
  2. getMarsRealEstateProperties() dodaj kod widoczny poniżej. Gdy pojawi się prośba, zaimportuj retrofit2.Callbackcom.example.android.marsrealestate.network.MarsApi.

    Metoda MarsApi.retrofitService.getProperties() zwraca obiekt Call. Następnie możesz wywołać na tym obiekcie funkcję enqueue(), aby rozpocząć żądanie sieciowe w wątku w tle.
MarsApi.retrofitService.getProperties().enqueue( 
   object: Callback<String> {
})
  1. Kliknij słowo object podkreślone na czerwono. Kliknij Kod > Implementuj metody. Wybierz z listy zarówno onResponse(), jak i onFailure().


    Android Studio dodaje kod z komentarzami TODO w każdej metodzie:
override fun onFailure(call: Call<String>, t: Throwable) {
       TODO("not implemented") 
}

override fun onResponse(call: Call<String>, 
   response: Response<String>) {
       TODO("not implemented") 
}
  1. onFailure() usuń TODO i ustaw _response na komunikat o błędzie, jak pokazano poniżej. _response to LiveData, który określa, co jest wyświetlane w widoku tekstowym. Każdy stan musi zaktualizować _response LiveData.

    Wywołanie zwrotne onFailure() jest wywoływane, gdy odpowiedź usługi internetowej nie powiedzie się. W przypadku tej odpowiedzi ustaw stan _response na "Failure: " połączony z wiadomością z argumentu Throwable.
override fun onFailure(call: Call<String>, t: Throwable) {
   _response.value = "Failure: " + t.message
}
  1. onResponse() usuń TODO i ustaw _response na treść odpowiedzi. Wywołanie zwrotne onResponse() jest wywoływane, gdy żądanie zostanie zrealizowane, a usługa internetowa zwróci odpowiedź.
override fun onResponse(call: Call<String>, 
   response: Response<String>) {
      _response.value = response.body()
}

Krok 4. Określ uprawnienia internetowe

  1. Skompiluj i uruchom aplikację MarsRealEstate. Zwróć uwagę, że aplikacja natychmiast się zamyka z błędem.
  2. W Android Studio kliknij kartę Logcat i zwróć uwagę na błąd w logu, który zaczyna się od wiersza podobnego do tego:
Process: com.example.android.marsrealestate, PID: 10646
java.lang.SecurityException: Permission denied (missing INTERNET permission?)

Komunikat o błędzie informuje, że w aplikacji może brakować uprawnienia INTERNET. Połączenie z internetem wiąże się z zagrożeniami dla bezpieczeństwa, dlatego aplikacje domyślnie nie mają połączenia z internetem. Musisz wyraźnie poinformować Androida, że aplikacja potrzebuje dostępu do internetu.

  1. Otwórz pokój app/manifests/AndroidManifest.xml. Dodaj ten wiersz tuż przed tagiem <application>:
<uses-permission android:name="android.permission.INTERNET" />
  1. Skompiluj i uruchom aplikację ponownie. Jeśli połączenie internetowe działa prawidłowo, zobaczysz tekst JSON zawierający dane dotyczące Mars Property.
  2. Aby zamknąć aplikację, kliknij przycisk Wstecz na urządzeniu lub emulatorze.
  3. Włącz tryb samolotowy na urządzeniu lub emulatorze, a następnie ponownie otwórz aplikację z menu Ostatnie lub uruchom ją ponownie w Android Studio.


  1. Wyłącz ponownie tryb samolotowy.

Teraz otrzymujesz odpowiedź JSON z usługi internetowej Mars, co jest dobrym początkiem. Ale tak naprawdę potrzebujesz obiektów Kotlin, a nie długiego ciągu JSON. Istnieje biblioteka o nazwie Moshi, która jest parserem JSON na Androida, który przekształca ciąg znaków JSON w obiekty Kotlin. Retrofit ma konwerter, który współpracuje z Moshi, więc jest to świetna biblioteka do Twoich celów.

W tym zadaniu użyjesz biblioteki Moshi z Retrofit, aby przeanalizować odpowiedź JSON z usługi internetowej i przekształcić ją w przydatne obiekty Kotlin Mars Property. Zmieniasz aplikację tak, aby zamiast wyświetlać nieprzetworzony kod JSON, wyświetlała liczbę zwróconych obiektów Mars Properties.

Krok 1. Dodaj zależności biblioteki Moshi

  1. Otwórz plik build.gradle (Module: app).
  2. W sekcji zależności dodaj kod podany poniżej, aby uwzględnić zależności Moshi. Podobnie jak w przypadku Retrofit, $version_moshi jest definiowany oddzielnie w pliku Gradle na poziomie projektu. Te zależności dodają obsługę podstawowej biblioteki JSON Moshi i obsługę Kotlina w Moshi.
implementation "com.squareup.moshi:moshi:$version_moshi"
implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"
  1. Znajdź wiersz konwertera skalarnego Retrofit w bloku dependencies:
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"
  1. Zmień ten wiersz, aby używać converter-moshi:
implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"
  1. Kliknij Synchronizuj teraz, aby ponownie utworzyć projekt z nowymi zależnościami.

Krok 2. Zaimplementuj klasę danych MarsProperty

Przykładowy wpis w odpowiedzi JSON otrzymanej z usługi internetowej wygląda tak:

[{"price":450000,
"id":"424906",
"type":"rent",
"img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"},
...]

Odpowiedź JSON pokazana powyżej to tablica, co wskazują nawiasy kwadratowe. Tablica zawiera obiekty JSON, które są otoczone nawiasami klamrowymi. Każdy obiekt zawiera zbiór par nazwa-wartość rozdzielonych dwukropkami. Nazwy są ujęte w cudzysłów. Wartości mogą być liczbami lub ciągami tekstowymi, a ciągi tekstowe są również ujęte w cudzysłów. Na przykład price dla tej nieruchomości wynosi 450 000 PLN, a img_src to adres URL, czyli lokalizacja pliku obrazu na serwerze.

W przykładzie powyżej każda pozycja dotycząca Marsa zawiera te pary klucz-wartość JSON:

  • price: cena nieruchomości na Marsie jako liczba.
  • id: identyfikator usługi w postaci ciągu znaków.
  • type: "rent" lub "buy".
  • img_src: adres URL obrazu w postaci ciągu znaków.

Moshi analizuje te dane JSON i konwertuje je na obiekty Kotlin. W tym celu musi mieć klasę danych Kotlin do przechowywania przeanalizowanych wyników, więc następnym krokiem jest utworzenie tej klasy.

  1. Otwórz pokój app/java/network/MarsProperty.kt.
  2. Zastąp istniejącą definicję klasy MarsProperty tym kodem:
data class MarsProperty(
   val id: String, val img_src: String,
   val type: String,
   val price: Double
)

Zwróć uwagę, że każda zmienna w klasie MarsProperty odpowiada nazwie klucza w obiekcie JSON. Aby dopasować typy w JSON, użyj obiektów String dla wszystkich wartości z wyjątkiem price, który jest Double. Wartość Double może reprezentować dowolną liczbę JSON.

Gdy Moshi analizuje JSON, dopasowuje klucze według nazwy i wypełnia obiekty danych odpowiednimi wartościami.

  1. Zastąp wiersz klawisza img_src wierszem pokazanym poniżej. W razie potrzeby zaimportuj com.squareup.moshi.Json.
@Json(name = "img_src") val imgSrcUrl: String,

Czasami nazwy kluczy w odpowiedzi JSON mogą powodować niejasne właściwości Kotlin lub nie pasować do Twojego stylu kodowania. Na przykład w pliku JSON klucz img_src używa podkreślenia, podczas gdy właściwości Kotlin zwykle używają wielkich i małych liter („camel case”).

Aby używać w klasie danych nazw zmiennych, które różnią się od nazw kluczy w odpowiedzi JSON, użyj adnotacji @Json. W tym przykładzie nazwa zmiennej w klasie danych to imgSrcUrl. Zmienna jest mapowana na atrybut JSON img_src za pomocą funkcji @Json(name = "img_src").

Krok 3. Zaktualizuj MarsApiService i OverviewViewModel

Po utworzeniu klasy danych MarsProperty możesz zaktualizować interfejs API sieci i ViewModel, aby uwzględnić dane Moshi.

  1. Otwórz pokój network/MarsApiService.kt. W przypadku elementu ScalarsConverterFactory mogą się pojawić błędy dotyczące brakującej klasy. Wynika to ze zmiany zależności Retrofit wprowadzonej w kroku 1. Jak najszybciej napraw te błędy.
  2. U góry pliku, tuż przed konstruktorem Retrofit, dodaj ten kod, aby utworzyć instancję Moshi. W razie potrzeby zaimportuj com.squareup.moshi.Moshicom.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory.
private val moshi = Moshi.Builder()
   .add(KotlinJsonAdapterFactory())
   .build()

Podobnie jak w przypadku Retrofit, tutaj tworzysz obiekt moshi za pomocą kreatora Moshi. Aby adnotacje Moshi działały prawidłowo w przypadku języka Kotlin, dodaj KotlinJsonAdapterFactory, a następnie wywołaj build().

  1. Zmień narzędzie do tworzenia Retrofit, aby używać MoshiConverterFactory zamiast ScalarConverterFactory, i przekaż utworzoną instancję moshi. W razie potrzeby zaimportuj retrofit2.converter.moshi.MoshiConverterFactory.
private val retrofit = Retrofit.Builder()
   .addConverterFactory(MoshiConverterFactory.create(moshi))
   .baseUrl(BASE_URL)
   .build()
  1. Usuń też import dla ScalarConverterFactory.

Kod do usunięcia:

import retrofit2.converter.scalars.ScalarsConverterFactory
  1. Zaktualizuj interfejs MarsApiService, aby Retrofit zwracał listę obiektów MarsProperty zamiast Call<String>.
interface MarsApiService {
   @GET("realestate")
   fun getProperties():
      Call<List<MarsProperty>>
}
  1. Otwórz pokój OverviewViewModel.kt. Przewiń w dół do wywołania metody getProperties().enqueue() w metodzie getMarsRealEstateProperties().
  2. Zmień argument na enqueue()Callback<String> na Callback<List<MarsProperty>>. W razie potrzeby zaimportuj com.example.android.marsrealestate.network.MarsProperty.
MarsApi.retrofitService.getProperties().enqueue( 
   object: Callback<List<MarsProperty>> {
  1. W funkcji onFailure() zmień argument z Call<String> na Call<List<MarsProperty>>:
override fun onFailure(call: Call<List<MarsProperty>>, t: Throwable) {
  1. Wprowadź tę samą zmianę w obu argumentach funkcji onResponse():
override fun onResponse(call: Call<List<MarsProperty>>, 
   response: Response<List<MarsProperty>>) {
  1. W treści onResponse() zastąp istniejące przypisanie do _response.value przypisaniem podanym poniżej. Ponieważ response.body() jest teraz listą obiektów MarsProperty, rozmiar tej listy to liczba przeanalizowanych właściwości. Ta wiadomość z odpowiedzią zawiera liczbę właściwości:
_response.value = 
   "Success: ${response.body()?.size} Mars properties retrieved"
  1. Upewnij się, że tryb samolotowy jest wyłączony. Skompiluj i uruchom aplikację. Tym razem w wiadomości powinna się wyświetlić liczba usług zwróconych przez usługę internetową:

Usługa interfejsu API Retrofit działa już, ale korzysta z wywołania zwrotnego z 2 metodami wywołania zwrotnego, które musisz zaimplementować. Jedna metoda obsługuje powodzenie, a druga – niepowodzenie. Wynik niepowodzenia zgłasza wyjątki. Kod byłby bardziej wydajny i czytelny, gdyby zamiast wywołań zwrotnych można było używać korutyn z obsługą wyjątków. Retrofit ma bibliotekę, która integruje korutyny.

W tym zadaniu przekształcisz usługę sieciową i ViewModel, aby korzystały z korutyn.

Krok 1. Dodaj zależności od procedur współbieżnych

  1. Otwórz plik build.gradle (Module: app).
  2. W sekcji zależności dodaj obsługę podstawowych bibliotek współprogramów Kotlin i biblioteki współprogramów Retrofit:
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"
  1. Kliknij Synchronizuj teraz, aby ponownie utworzyć projekt z nowymi zależnościami.

Krok 2. Zaktualizuj MarsApiService i OverviewViewModel

  1. MarsApiService.kt zaktualizuj narzędzie do tworzenia Retrofit, aby używać CoroutineCallAdapterFactory. Pełny kreator wygląda teraz tak:
private val retrofit = Retrofit.Builder()
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .addCallAdapterFactory(CoroutineCallAdapterFactory())
        .baseUrl(BASE_URL)
        .build()

Adaptery wywołań umożliwiają Retrofitowi tworzenie interfejsów API, które zwracają coś innego niż domyślna klasa Call. W tym przypadku CoroutineCallAdapterFactory pozwala nam zastąpić obiekt Call, który zwraca getProperties(), obiektem Deferred.

  1. W metodzie getProperties() zmień Call<List<MarsProperty>> na Deferred<List<MarsProperty>>. W razie potrzeby zaimportuj kotlinx.coroutines.Deferred. Pełna metoda getProperties() wygląda tak:
@GET("realestate")
fun getProperties():
   Deferred<List<MarsProperty>>

Interfejs Deferred definiuje zadanie korutyny, które zwraca wartość wyniku (Deferred dziedziczy z Job). Interfejs Deferred zawiera metodę o nazwie await(), która powoduje, że kod czeka bez blokowania, aż wartość będzie gotowa, a następnie zwraca tę wartość.

  1. Otwórz pokój OverviewViewModel.kt. Tuż przed blokiem init dodaj zadanie korutyny:
private var viewModelJob = Job()
  1. Utwórz zakres coroutine dla nowego zadania za pomocą głównego dyspozytora:
private val coroutineScope = CoroutineScope(
   viewModelJob + Dispatchers.Main )

Dispatchers.Main Dyspozytor używa wątku interfejsu do wykonywania swojej pracy. Retrofit wykonuje wszystkie działania w wątku w tle, więc nie ma potrzeby używania innego wątku w przypadku zakresu. Dzięki temu możesz łatwo zaktualizować wartość MutableLiveData, gdy uzyskasz wynik.

  1. Usuń cały kod w getMarsRealEstateProperties(). Zamiast wywołania funkcji enqueue() oraz wywołań zwrotnych onFailure()onResponse() użyjesz tutaj korutyn.
  2. W funkcji getMarsRealEstateProperties() uruchom korutynę:
coroutineScope.launch { 

}


Aby użyć obiektu Deferred, który Retrofit zwraca w przypadku zadania sieciowego, musisz znajdować się w korutynie, więc tutaj uruchamiasz utworzoną korutynę. Kod jest nadal wykonywany w wątku głównym, ale teraz współbieżnością zarządzają korutyny.

  1. W bloku uruchamiania wywołaj funkcję getProperties() na obiekcie retrofitService:
var getPropertiesDeferred = MarsApi.retrofitService.getProperties()

Wywołanie funkcji getProperties() z usługi MarsApi tworzy i uruchamia połączenie sieciowe w wątku w tle, zwracając obiekt Deferred dla tego zadania.

  1. W bloku uruchamiania dodaj też blok try/catch, aby obsługiwać wyjątki:
try {

} catch (e: Exception) {
  
}
  1. W bloku try {} wywołaj funkcję await() na obiekcie Deferred:
var listResult = getPropertiesDeferred.await()

Wywołanie await() na obiekcie Deferred zwraca wynik wywołania sieciowego, gdy wartość jest gotowa. Metoda await() nie blokuje wątku, więc usługa Mars API pobiera dane z sieci bez blokowania bieżącego wątku, co jest ważne, ponieważ znajdujemy się w zakresie wątku interfejsu. Po wykonaniu zadania kod jest wykonywany dalej od miejsca, w którym został przerwany. Dzieje się to w bloku try {}, dzięki czemu możesz przechwytywać wyjątki.

  1. W bloku try {}, po metodzie await(), zaktualizuj wiadomość z odpowiedzią w przypadku pozytywnej odpowiedzi:
_response.value = 
   "Success: ${listResult.size} Mars properties retrieved"
  1. W bloku catch {} obsłuż odpowiedź o błędzie:
_response.value = "Failure: ${e.message}"


Pełna metoda getMarsRealEstateProperties() wygląda teraz tak:

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}"
       }
   }
}
  1. U dołu klasy dodaj wywołanie zwrotne onCleared() z tym kodem:
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}

Wczytywanie danych powinno się zatrzymać, gdy ViewModel zostanie zniszczony, ponieważ OverviewFragment, który go używa, zniknie.ViewModel Aby zatrzymać ładowanie, gdy element ViewModel zostanie zniszczony, zastąp element onCleared(), aby anulować zadanie.

  1. Skompiluj i uruchom aplikację. Tym razem uzyskasz ten sam wynik co w poprzednim zadaniu (raport z liczbą właściwości), ale z bardziej przejrzystym kodem i obsługą błędów.

Projekt Android Studio: MarsRealEstateNetwork

Usługi internetowe REST

  • Usługa internetowa to usługa w internecie, która umożliwia aplikacji wysyłanie żądań i otrzymywanie danych.
  • Typowe usługi internetowe korzystają z architektury REST. Usługi internetowe, które oferują architekturę REST, są znane jako usługi RESTful. Usługi internetowe RESTful są tworzone przy użyciu standardowych komponentów i protokołów internetowych.
  • Żądanie do usługi internetowej REST wysyłasz w standardowy sposób za pomocą identyfikatorów URI.
  • Aby korzystać z usługi internetowej, aplikacja musi nawiązać połączenie sieciowe i komunikować się z nią. Następnie aplikacja musi otrzymać i przeanalizować dane odpowiedzi w formacie, którego może używać.
  • Biblioteka Retrofit to biblioteka klienta, która umożliwia aplikacji wysyłanie żądań do usługi internetowej REST.
  • Używaj konwerterów, aby określać, co Retrofit ma robić z danymi wysyłanymi do usługi internetowej i z niej odbieranymi. Na przykład konwerter ScalarsConverter traktuje dane usługi internetowej jako String lub inny typ prosty.
  • Aby umożliwić aplikacji łączenie się z internetem, dodaj uprawnienie "android.permission.INTERNET" do manifestu Androida.

Analiza JSON

  • Odpowiedź usługi internetowej jest często formatowana w JSON, czyli popularnym formacie wymiany danych strukturalnych.
  • Obiekt JSON to zbiór par klucz-wartość. Ta kolekcja jest czasami nazywana słownikiem, mapą skrótów lub tablicą asocjacyjną.
  • Zbiór obiektów JSON to tablica JSON. W odpowiedzi z usługi internetowej otrzymujesz tablicę JSON.
  • Klucze w parze klucz-wartość są ujęte w cudzysłów. Wartości mogą być liczbami lub ciągami znaków. Ciągi znaków są również ujęte w cudzysłów.
  • Biblioteka Moshi to parser JSON na Androida, który konwertuje ciąg JSON na obiekty Kotlin. Retrofit ma konwerter, który działa z Moshi.
  • Moshi dopasowuje klucze w odpowiedzi JSON do właściwości w obiekcie danych, które mają tę samą nazwę.
  • Aby użyć innej nazwy właściwości dla klucza, dodaj do tej właściwości adnotację @Json i nazwę klucza JSON.

Retrofit i korutyny

  • Adaptery wywołań umożliwiają Retrofitowi tworzenie interfejsów API, które zwracają coś innego niż domyślna klasa Call. Użyj klasy CoroutineCallAdapterFactory, aby zastąpić Call korutyną Deferred.
  • Użyj metody await() na obiekcie Deferred, aby kod korutyny czekał bez blokowania, aż wartość będzie gotowa, a następnie zwróci tę wartość.

Kurs Udacity:

Dokumentacja dla deweloperów aplikacji na Androida:

Dokumentacja języka Kotlin:

Inne:

W tej sekcji znajdziesz listę możliwych zadań domowych dla uczniów, którzy wykonują ten moduł w ramach kursu prowadzonego przez instruktora. Nauczyciel musi:

  • W razie potrzeby przypisz pracę domową.
  • Poinformuj uczniów, jak przesyłać projekty.
  • Oceń zadania domowe.

Instruktorzy mogą korzystać z tych sugestii w dowolnym zakresie i mogą zadawać inne zadania domowe, które uznają za odpowiednie.

Jeśli wykonujesz ten kurs samodzielnie, możesz użyć tych zadań domowych, aby sprawdzić swoją wiedzę.

Odpowiedz na te pytania

Pytanie 1

Jakie 2 kluczowe elementy są potrzebne bibliotece Retrofit do utworzenia interfejsu API usług internetowych?

▢ Podstawowy identyfikator URI usługi internetowej i zapytanie GET.

▢ Podstawowy identyfikator URI usługi internetowej i fabryka konwerterów.

▢ Połączenie sieciowe z usługą internetową i token autoryzacji.

▢ fabryka konwerterów i parser odpowiedzi;

Pytanie 2

Do czego służy biblioteka Moshi?

▢ Aby odzyskać dane z usługi internetowej.

▢ Do interakcji z Retrofit w celu wysłania żądania usługi internetowej.

▢ Aby przeanalizować odpowiedź JSON z usługi internetowej i przekształcić ją w obiekty danych Kotlin.

▢ Aby zmienić nazwy obiektów Kotlin, tak aby pasowały do kluczy w odpowiedzi JSON.

Pytanie 3

Do czego służą adaptery połączeń Retrofit?

▢ Umożliwiają Retrofitowi korzystanie z korutyn.

▢ Przekształcają odpowiedź usługi internetowej w obiekty danych Kotlin.

▢ Zmieniają wywołanie Retrofit na wywołanie usługi internetowej.

▢ Umożliwiają zwracanie w Retrofit innych elementów niż domyślna klasa Call.

Rozpocznij kolejną lekcję: 8.2 Wczytywanie i wyświetlanie obrazów z internetu

Linki do innych ćwiczeń z tego kursu znajdziesz na stronie docelowej ćwiczeń z podstaw języka Kotlin na Androidzie.