Android Kotlin Fundamentals 08.3 Filtering and detail views with internet data

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

W poprzednich ćwiczeniach z programowania w ramach tej lekcji dowiedzieliśmy się, jak pobierać dane o nieruchomościach na Marsie z usługi internetowej i jak tworzyć RecyclerView z układem siatki, aby wczytywać i wyświetlać obrazy z tych danych. W tym laboratorium dokończysz aplikację MarsRealEstate, implementując możliwość filtrowania nieruchomości na Marsie według tego, czy są dostępne do wynajęcia czy do kupienia. Tworzysz też widok szczegółowy, aby po kliknięciu przez użytkownika zdjęcia nieruchomości w widoku ogólnym wyświetlił się widok szczegółowy z informacjami o tej nieruchomości.

Co warto wiedzieć

  • Jak tworzyć i używać fragmentów.
  • Jak przechodzić między fragmentami i używać Safe Args (wtyczki Gradle) do przekazywania danych między fragmentami.
  • Jak używać komponentów architektury, w tym modeli widoku, fabryk modeli widoku, przekształceń i LiveData.
  • Dowiedz się, jak pobierać dane zakodowane w formacie JSON z usługi internetowej REST i przetwarzać je na obiekty Kotlin za pomocą bibliotek RetrofitMoshi.

Czego się nauczysz

  • Jak używać złożonych wyrażeń wiązania w plikach układu.
  • Jak wysyłać żądania Retrofit do usługi internetowej z opcjami zapytań.

Co musisz zrobić

  • Zmodyfikuj aplikację MarsRealEstate, aby oznaczać nieruchomości na Marsie, które są na sprzedaż (a nie na wynajem), ikoną dolara.
  • W menu opcji na stronie przeglądu utwórz żądanie usługi internetowej, które filtruje właściwości Marsa według typu.
  • Utwórz fragment szczegółowy dla usługi Mars, połącz go z siatką przeglądu za pomocą nawigacji i przekaż do niego dane usługi.

W tym ćwiczeniu z programowania (i powiązanych z nim ćwiczeniach) będziesz pracować z aplikacją MarsRealEstate, która wyświetla nieruchomości na sprzedaż na Marsie. Aplikacja łączy się z serwerem internetowym, aby pobierać i wyświetlać dane dotyczące 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. W poprzednich ćwiczeniach utworzyliśmy RecyclerView z układem siatki dla wszystkich zdjęć obiektu:

W tej wersji aplikacji pracujesz z typem nieruchomości (na wynajem lub na sprzedaż) i dodajesz ikonę do układu siatki, aby oznaczyć nieruchomości na sprzedaż:

Zmodyfikuj menu opcji aplikacji, aby filtrować siatkę i wyświetlać tylko te nieruchomości, które są na wynajem lub na sprzedaż:

Na koniec tworzysz widok szczegółowy dla poszczególnych usług i łączysz ikony w siatce przeglądu z tym fragmentem szczegółowym za pomocą nawigacji:

Do tej pory jedyną częścią danych obiektu Mars, której używasz, jest adres URL obrazu obiektu. Dane usługi, które zostały zdefiniowane w MarsProperty, obejmują też identyfikator, cenę i typ (wynajem lub sprzedaż). Przypomnijmy sobie fragment danych JSON, które otrzymujesz z usługi internetowej:

{
   "price":8000000,
   "id":"424908",
   "type":"rent",
   "img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg"
},

W tym zadaniu zaczniesz pracować z typem usługi Mars, aby dodać obraz znaku dolara do usług na stronie przeglądu, które są na sprzedaż.

Krok 1. Zaktualizuj MarsProperty, aby uwzględnić typ

Klasa MarsProperty określa strukturę danych dla każdej usługi udostępnianej przez usługę internetową. W poprzednim samouczku używaliśmy biblioteki Moshi do analizowania surowej odpowiedzi JSON z usługi internetowej Mars i przekształcania jej w poszczególne obiekty danych MarsProperty.

W tym kroku dodasz do klasy MarsProperty logikę, która będzie wskazywać, czy nieruchomość jest na wynajem (czyli czy typ to ciąg znaków "rent" lub "buy"). Będziesz używać tej logiki w kilku miejscach, więc lepiej umieścić ją w klasie danych niż ją powielać.

  1. Otwórz aplikację MarsRealEstate z ostatnich zajęć. (Jeśli nie masz aplikacji, możesz pobrać MarsRealEstateGrid).
  2. Otwórz pokój network/MarsProperty.kt. Dodaj treść do definicji klasy MarsProperty i dodaj niestandardowy getter dla isRental, który zwraca true, jeśli obiekt jest typu "rent".
data class MarsProperty(
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double)  {
   val isRental
       get() = type == "rent"
}

Krok 2. Zaktualizuj układ elementu siatki

Teraz zaktualizuj układ elementu w przypadku siatki obrazów, aby symbol dolara był widoczny tylko na tych zdjęciach nieruchomości, które są na sprzedaż:

Dzięki wyrażeniom wiązania danych możesz przeprowadzić ten test w całości w pliku układu XML elementów siatki.

  1. Otwórz pokój res/layout/grid_view_item.xml. Jest to plik układu dla każdej komórki w układzie siatki w przypadku RecyclerView. Obecnie plik zawiera tylko element <ImageView> dla obrazu usługi.
  2. Wewnątrz elementu <data> dodaj element <import> dla klasy View. Importy są używane, gdy chcesz użyć komponentów klasy w wyrażeniu wiązania danych w pliku układu. W tym przypadku użyjesz stałych View.GONEView.VISIBLE, więc musisz mieć dostęp do klasy View.
<import type="android.view.View"/>
  1. Obejmij cały widok obrazu tagiem FrameLayout, aby umożliwić umieszczenie rysunku znaku dolara na obrazie nieruchomości.
<FrameLayout
   android:layout_width="match_parent"
   android:layout_height="170dp">
             <ImageView 
                    android:id="@+id/mars_image"
            ...
</FrameLayout>
  1. W przypadku elementu ImageView zmień atrybut android:layout_height na match_parent, aby wypełnić nowy element nadrzędny FrameLayout.
android:layout_height="match_parent"
  1. Dodaj drugi element <ImageView> tuż pod pierwszym, w sekcji FrameLayout. Skorzystaj z definicji podanej poniżej. Ten obraz pojawia się w prawym dolnym rogu elementu siatki, nad obrazem Marsa, i używa elementu rysowalnego zdefiniowanego w res/drawable/ic_for_sale_outline.xml dla ikony dolara.
<ImageView
   android:id="@+id/mars_property_type"
   android:layout_width="wrap_content"
   android:layout_height="45dp"
   android:layout_gravity="bottom|end"
   android:adjustViewBounds="true"
   android:padding="5dp"
   android:scaleType="fitCenter"
   android:src="@drawable/ic_for_sale_outline"
   tools:src="@drawable/ic_for_sale_outline"/>
  1. Dodaj atrybut android:visibility do widoku obrazu mars_property_type. Użyj wyrażenia wiążącego, aby sprawdzić typ usługi, i przypisz widoczność do wartości View.GONE (w przypadku wypożyczenia) lub View.VISIBLE (w przypadku zakupu).
 android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"

Do tej pory wyrażenia wiążące były widoczne tylko w układach, które używają poszczególnych zmiennych zdefiniowanych w elemencie <data>. Wyrażenia wiążące są bardzo przydatne i umożliwiają wykonywanie operacji takich jak testy i obliczenia matematyczne w układzie XML. W tym przypadku używasz operatora trójargumentowego (?:), aby przeprowadzić test (czy ten obiekt jest wypożyczalnią?). Podajesz 1 wynik dla wartości „prawda” (ukryj ikonę dolara za pomocą View.GONE) i 1 wynik dla wartości „fałsz” (wyświetl tę ikonę za pomocą View.VISIBLE).

Nowy, kompletny plik grid_view_item.xml jest widoczny poniżej:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:app="http://schemas.android.com/apk/res-auto"
       xmlns:tools="http://schemas.android.com/tools">
   <data>
       <import type="android.view.View"/>
       <variable
           name="property"
           type="com.example.android.marsrealestate.network.MarsProperty" />
   </data>
   <FrameLayout
       android:layout_width="match_parent"
       android:layout_height="170dp">

       <ImageView
           android:id="@+id/mars_image"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           android:scaleType="centerCrop"
           android:adjustViewBounds="true"
           android:padding="2dp"
           app:imageUrl="@{property.imgSrcUrl}"
           tools:src="@tools:sample/backgrounds/scenic"/>

       <ImageView
           android:id="@+id/mars_property_type"
           android:layout_width="wrap_content"
           android:layout_height="45dp"
           android:layout_gravity="bottom|end"
           android:adjustViewBounds="true"
           android:padding="5dp"
           android:scaleType="fitCenter"
           android:src="@drawable/ic_for_sale_outline"
           android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"
           tools:src="@drawable/ic_for_sale_outline"/>
   </FrameLayout>
</layout>
  1. Skompiluj i uruchom aplikację. Zwróć uwagę, że obiekty, które nie są wynajmowane, mają ikonę dolara.

Obecnie w siatce przeglądu aplikacja wyświetla wszystkie usługi Mars. Jeśli użytkownik szuka nieruchomości na Marsie, ikony wskazujące, które z dostępnych nieruchomości są na sprzedaż, byłyby przydatne, ale na stronie nadal jest wiele nieruchomości, przez które trzeba przewijać. W tym zadaniu dodasz do fragmentu przeglądu menu opcji, które umożliwi użytkownikowi wyświetlanie tylko wynajmowanych nieruchomości, tylko nieruchomości na sprzedaż lub wszystkich nieruchomości.

Możesz to zrobić, testując typ każdego elementu MarsProperty w siatce przeglądu i wyświetlając tylko pasujące właściwości. Prawdziwa usługa internetowa Mars ma jednak parametr zapytania lub opcję (o nazwie filter), która umożliwia pobieranie tylko właściwości typu rent lub buy. Możesz użyć tego zapytania filtra z adresem URL usługi internetowej realestate w przeglądarce w ten sposób:

https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buy

W tym zadaniu zmodyfikujesz klasę MarsApiService, aby dodać opcję zapytania do żądania usługi internetowej za pomocą Retrofit. Następnie podłączasz menu opcji, aby ponownie pobrać wszystkie dane dotyczące nieruchomości na Marsie za pomocą tej opcji zapytania. Odpowiedź z usługi internetowej zawiera tylko właściwości, które Cię interesują, więc nie musisz w ogóle zmieniać logiki wyświetlania widoku w siatce przeglądu.

Krok 1. Zaktualizuj usługę Mars API

Aby zmienić żądanie, musisz wrócić do klasy MarsApiService, którą zaimplementowano w pierwszym ćwiczeniu z tej serii. Modyfikujesz klasę, aby udostępnić interfejs API filtrowania.

  1. Otwórz pokój network/MarsApiService.kt. Tuż pod instrukcjami importu utwórz enum o nazwie MarsApiFilter, aby zdefiniować stałe pasujące do wartości zapytań, których oczekuje usługa internetowa.
enum class MarsApiFilter(val value: String) {
   SHOW_RENT("rent"),
   SHOW_BUY("buy"),
   SHOW_ALL("all") }
  1. Zmodyfikuj metodę getProperties(), aby przyjmowała ciąg znaków jako dane wejściowe zapytania filtra, i oznacz te dane wejściowe za pomocą @Query("filter"), jak pokazano poniżej.

    Gdy pojawi się prośba, zaimportuj retrofit2.http.Query.

    Adnotacja @Query informuje metodę getProperties() (a tym samym Retrofit), aby wysłać żądanie do usługi internetowej z opcją filtra. Za każdym razem, gdy wywoływana jest funkcja getProperties(), adres URL żądania zawiera część ?filter=type, która nakazuje usłudze internetowej zwrócenie wyników pasujących do tego zapytania.
fun getProperties(@Query("filter") type: String):  

Krok 2. Zaktualizuj model widoku ogólnego

W metodzie getMarsRealEstateProperties()OverviewViewModel wysyłasz do MarsApiService prośbę o dane. Teraz musisz zaktualizować tę prośbę, aby uwzględnić argument filtra.

  1. Otwórz pokój overview/OverviewViewModel.kt. W Android Studio zobaczysz błędy spowodowane zmianami wprowadzonymi w poprzednim kroku. Dodaj MarsApiFilter (wyliczenie możliwych wartości filtra) jako parametr do wywołania getMarsRealEstateProperties().

    Zaimportuj com.example.android.marsrealestate.network.MarsApiFilter, gdy pojawi się prośba.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
  1. Zmodyfikuj wywołanie getProperties() w usłudze Retrofit, aby przekazywać to zapytanie filtra jako ciąg znaków.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
  1. W bloku init {} przekaż MarsApiFilter.SHOW_ALL jako argument do getMarsRealEstateProperties(), aby wyświetlić wszystkie usługi przy pierwszym wczytaniu aplikacji.
init {
   getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
  1. Na końcu klasy dodaj metodę updateFilter(), która przyjmuje argument MarsApiFilter i wywołuje metodę getMarsRealEstateProperties() z tym argumentem.
fun updateFilter(filter: MarsApiFilter) {
   getMarsRealEstateProperties(filter)
}

Krok 3. Połącz fragment z menu opcji

Ostatnim krokiem jest połączenie menu dodatkowego z fragmentem, aby wywoływać funkcję updateFilter() w modelu widoku, gdy użytkownik wybierze opcję menu.

  1. Otwórz pokój res/menu/overflow_menu.xml. Aplikacja MarsRealEstate ma menu dodatkowe z 3 opcjami: wyświetlanie wszystkich nieruchomości, wyświetlanie tylko nieruchomości na wynajem i wyświetlanie tylko nieruchomości na sprzedaż.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
   <item
       android:id="@+id/show_all_menu"
       android:title="@string/show_all" />
   <item
       android:id="@+id/show_rent_menu"
       android:title="@string/show_rent" />
   <item
       android:id="@+id/show_buy_menu"
       android:title="@string/show_buy" />
</menu>
  1. Otwórz pokój overview/OverviewFragment.kt. Na koniec zajęć zaimplementuj metodę onOptionsItemSelected(), aby obsługiwać wybór pozycji menu.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. onOptionsItemSelected() wywołaj metodę updateFilter() w modelu widoku z odpowiednim filtrem. Użyj bloku when {} w języku Kotlin, aby przełączać się między opcjami. Użyj MarsApiFilter.SHOW_ALL jako domyślnej wartości filtra. Zwróć true, ponieważ element menu został obsłużony. Na żądanie zaimportuj MarsApiFilter (com.example.android.marsrealestate.network.MarsApiFilter). Pełna metoda onOptionsItemSelected() jest pokazana poniżej.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
   viewModel.updateFilter(
           when (item.itemId) {
               R.id.show_rent_menu -> MarsApiFilter.SHOW_RENT
               R.id.show_buy_menu -> MarsApiFilter.SHOW_BUY
               else -> MarsApiFilter.SHOW_ALL
           }
   )
   return true
}
  1. Skompiluj i uruchom aplikację. Aplikacja uruchomi pierwszą siatkę przeglądową ze wszystkimi typami nieruchomości, a nieruchomości na sprzedaż będą oznaczone ikoną dolara.
  2. W menu opcji wybierz Wypożycz. Usługi zostaną ponownie załadowane i przy żadnej z nich nie będzie widoczna ikona dolara. (Wyświetlane są tylko obiekty do wynajęcia). Odświeżenie ekranu i wyświetlenie tylko odfiltrowanych usług może potrwać kilka chwil.
  3. W menu opcji wybierz Kup. Usługi zostaną ponownie załadowane i przy każdej z nich pojawi się ikona dolara. (Wyświetlane są tylko nieruchomości na sprzedaż).

Teraz masz przewijaną siatkę ikon właściwości Marsa, ale czas na więcej szczegółów. W tym zadaniu dodasz fragment szczegółów, aby wyświetlić informacje o konkretnej nieruchomości. W szczegółowym fragmencie wyświetli się większy obraz, cena i rodzaj nieruchomości – czy jest ona na wynajem, czy na sprzedaż.

Ten fragment jest uruchamiany, gdy użytkownik kliknie obraz w siatce podglądu. Aby to zrobić, musisz dodać onClick do RecyclerView elementów siatki detektor, a następnie przejść do nowego fragmentu. Nawigacja odbywa się przez wywołanie zmiany LiveDataViewModel, tak jak w przypadku poprzednich lekcji. Użyjesz też wtyczki Safe Args komponentu Navigation, aby przekazać wybrane informacje MarsProperty z fragmentu z ogólnymi informacjami do fragmentu ze szczegółami.

Krok 1. Utwórz model widoku szczegółów i zaktualizuj układ szczegółów

Podobnie jak w przypadku modelu widoku i fragmentów widoku ogólnego musisz teraz zaimplementować model widoku i pliki układu dla fragmentu szczegółów.

  1. Otwórz pokój detail/DetailViewModel.kt. Podobnie jak pliki Kotlin związane z siecią znajdują się w folderze network, a pliki przeglądu w folderze overview, folder detail zawiera pliki powiązane z widokiem szczegółowym. Zwróć uwagę, że klasa DetailViewModel (obecnie pusta) przyjmuje w konstruktorze parametr marsProperty.
class DetailViewModel( marsProperty: MarsProperty,
                     app: Application) : AndroidViewModel(app) {
}
  1. W definicji klasy dodaj LiveData dla wybranej właściwości Mars, aby udostępnić te informacje w widoku szczegółowym. Postępuj zgodnie ze zwykłym wzorcem tworzenia MutableLiveData, które będzie zawierać samo MarsProperty, a następnie udostępnij niezmienną publiczną właściwość LiveData.

    Zaimportuj androidx.lifecycle.LiveDataandroidx.lifecycle.MutableLiveData, gdy pojawi się prośba.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. Utwórz blok init {} i ustaw wartość wybranej właściwości Mars za pomocą obiektu MarsProperty z konstruktora.
    init {
        _selectedProperty.value = marsProperty
    }
  1. Otwórz res/layout/fragment_detail.xml i wyświetl go w widoku projektu.

    Jest to plik układu fragmentu szczegółów. Zawiera ImageView duże zdjęcie, TextView typ nieruchomości (na wynajem lub na sprzedaż) i TextView cenę. Zwróć uwagę, że układ ograniczeń jest opakowany elementem ScrollView, dzięki czemu będzie się automatycznie przewijać, jeśli widok stanie się zbyt duży, aby zmieścić się na ekranie, np. gdy użytkownik wyświetli go w trybie poziomym.
  2. Otwórz kartę Tekst w przypadku układu. U góry układu, tuż przed elementem <ScrollView>, dodaj element <data>, aby powiązać model widoku szczegółów z układem.
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
  1. Dodaj atrybut app:imageUrl do elementu ImageView. Ustaw go na imgSrcUrl z wybranej usługi modelu widoku.

    Adapter powiązania, który wczytuje obraz za pomocą Glide, będzie tu również używany automatycznie, ponieważ obserwuje wszystkie atrybuty app:imageUrl.
 app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"

Krok 2. Określ nawigację w modelu widoku przeglądu

Gdy użytkownik kliknie zdjęcie w modelu przeglądu, powinno to spowodować przejście do fragmentu, który zawiera szczegóły klikniętego elementu.

  1. Otwórz pokój overview/OverviewViewModel.kt. Dodaj właściwość _navigateToSelectedProperty MutableLiveData i udostępnij ją za pomocą niezmiennej wartości LiveData.

    Gdy wartość LiveData zmieni się na inną niż null, nastąpi nawigacja. (Wkrótce dodasz kod, aby obserwować tę zmienną i wywoływać nawigację).
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
   get() = _navigateToSelectedProperty
  1. Na końcu klasy dodaj metodę displayPropertyDetails(), która ustawia _navigateToSelectedProperty na wybraną właściwość Marsa.
fun displayPropertyDetails(marsProperty: MarsProperty) {
   _navigateToSelectedProperty.value = marsProperty
}
  1. Dodaj metodę displayPropertyDetailsComplete(), która ustawia wartość _navigateToSelectedProperty na null. Jest to potrzebne, aby oznaczyć stan nawigacji jako ukończony i uniknąć ponownego wywołania nawigacji, gdy użytkownik wróci z widoku szczegółów.
fun displayPropertyDetailsComplete() {
   _navigateToSelectedProperty.value = null
}

Krok 3. Skonfiguruj odbiorniki kliknięć w adapterze siatki i fragmencie

  1. Otwórz pokój overview/PhotoGridAdapter.kt. Na końcu zajęć utwórz niestandardową klasę OnClickListener, która przyjmuje lambdę z parametrem marsProperty. W klasie zdefiniuj funkcję onClick(), która jest ustawiona na parametr lambda.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
     fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
  1. Przewiń w górę do definicji klasy PhotoGridAdapter i dodaj do konstruktora prywatną właściwość OnClickListener.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. Spraw, aby zdjęcie było klikalne, dodając onClickListener do elementu siatki w metodzie onBindviewHolder(). Zdefiniuj odbiornik kliknięć między wywołaniami funkcji getItem() and bind().
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
   val marsProperty = getItem(position)
   holder.itemView.setOnClickListener {
       onClickListener.onClick(marsProperty)
   }
   holder.bind(marsProperty)
}
  1. Otwórz pokój overview/OverviewFragment.kt. W metodzie onCreateView() zastąp wiersz, który inicjuje właściwość binding.photosGrid.adapter, wierszem podanym poniżej.

    Ten kod dodaje obiekt PhotoGridAdapter.onClickListener do konstruktora PhotoGridAdapter i wywołuje funkcję viewModel.displayPropertyDetails() z przekazanym obiektem MarsProperty. Spowoduje to wywołanie funkcji LiveData w modelu widoku nawigacji.
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
   viewModel.displayPropertyDetails(it)
})

Krok 4. Zmodyfikuj wykres nawigacji i spraw, aby MarsProperty można było przekazywać

Gdy użytkownik kliknie zdjęcie w siatce podglądu, aplikacja powinna przejść do fragmentu szczegółów i przekazać szczegóły wybranej nieruchomości na Marsie, aby widok szczegółów mógł wyświetlić te informacje.

Obecnie masz odbiornik kliknięć z PhotoGridAdapter, który obsługuje kliknięcie, oraz sposób wywoływania nawigacji z modelu widoku. Nie masz jeszcze obiektu MarsProperty przekazywanego do fragmentu szczegółów. W tym celu użyj Safe Args z komponentu nawigacji.

  1. Otwórz pokój res/navigation/nav_graph.xml. Kliknij kartę Tekst, aby wyświetlić kod XML wykresu nawigacji.
  2. W elemencie <fragment> fragmentu szczegółów dodaj element <argument> pokazany poniżej. Ten argument, o nazwie selectedProperty, jest typu MarsProperty.
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. Skompiluj aplikację. Nawigacja zgłasza błąd, ponieważ MarsProperty nie jest możliwy do przekazania. Interfejs Parcelable umożliwia serializację obiektów, dzięki czemu dane obiektów mogą być przekazywane między fragmentami lub aktywnościami. W tym przypadku, aby dane w obiekcie MarsProperty mogły zostać przekazane do fragmentu szczegółów za pomocą Safe Args, obiekt MarsProperty musi implementować interfejs Parcelable. Dobra wiadomość jest taka, że Kotlin udostępnia łatwy skrót do implementowania tego interfejsu.
  2. Otwórz pokój network/MarsProperty.kt. Dodaj adnotację @Parcelize do definicji klasy.

    W razie potrzeby zaimportuj kotlinx.android.parcel.Parcelize.

    Adnotacja @Parcelize używa rozszerzeń Kotlin Android do automatycznego wdrażania metod w Parcelable interfejsie dla tej klasy. Nie musisz nic więcej robić.
@Parcelize
data class MarsProperty (
  1. Zmień definicję klasy MarsProperty, aby rozszerzyć Parcelable.

    Na żądanie zaimportuj android.os.Parcelable.

    Definicja klasy MarsProperty wygląda teraz tak:
@Parcelize
data class MarsProperty (
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double) : Parcelable {

Krok 5. Połącz fragmenty

Nadal nie nawigujesz – rzeczywista nawigacja odbywa się we fragmentach. W tym kroku dodasz ostatnie elementy, które umożliwią nawigację między fragmentami przeglądu i szczegółów.

  1. Otwórz pokój overview/OverviewFragment.kt. W onCreateView() poniżej wierszy, które inicjują adapter siatki zdjęć, dodaj wiersze pokazane poniżej, aby obserwować navigatedToSelectedProperty z modelu widoku przeglądu.

    Wpisz androidx.lifecycle.Observerandroidx.navigation.fragment.findNavController, gdy pojawi się prośba.

    Obserwator sprawdza, czy MarsProperty – it w funkcji lambda – nie ma wartości null. Jeśli tak, pobiera kontroler nawigacji z fragmentu z findNavController(). Wywołaj displayPropertyDetailsComplete(), aby poinformować model widoku o zresetowaniu LiveData do stanu null, dzięki czemu po powrocie aplikacji do OverviewFragment nie nastąpi przypadkowe ponowne uruchomienie nawigacji.
viewModel.navigateToSelectedProperty.observe(this, Observer {
   if ( null != it ) {   
      this.findNavController().navigate(
              OverviewFragmentDirections.actionShowDetail(it))             
      viewModel.displayPropertyDetailsComplete()
   }
})
  1. Otwórz pokój detail/DetailFragment.kt. Dodaj ten wiersz tuż pod wywołaniem funkcji setLifecycleOwner() w metodzie onCreateView(). Ta linia pobiera wybrany MarsPropertyobiekt z Safe Args

    .Zwróć uwagę na użycie operatora potwierdzenia, że wartość nie jest null (!!). Jeśli selectedProperty nie ma, stało się coś strasznego i chcesz, aby kod zgłosił wskaźnik null. (W kodzie produkcyjnym należy w jakiś sposób obsłużyć ten błąd).
 val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
  1. Następnie dodaj ten wiersz, aby uzyskać nowy DetailViewModelFactory. Użyj DetailViewModelFactory, aby uzyskać instancję DetailViewModel. Aplikacja startowa zawiera implementację DetailViewModelFactory, więc wystarczy ją zainicjować.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
  1. Na koniec dodaj tę linię, aby uzyskać DetailViewModel z fabryki i połączyć wszystkie części.
      binding.viewModel = ViewModelProviders.of(
                this, viewModelFactory).get(DetailViewModel::class.java)
  1. Skompiluj i uruchom aplikację, a potem kliknij dowolne zdjęcie nieruchomości na Marsie. Pojawi się fragment ze szczegółami tej usługi. Kliknij przycisk Wstecz, aby wrócić na stronę przeglądu. Zauważ, że ekran szczegółów jest nadal dość pusty. W następnym zadaniu dokończysz dodawanie danych usługi na tej stronie szczegółów.

Obecnie na stronie z informacjami wyświetlane jest tylko to samo zdjęcie Marsa, które zwykle widzisz na stronie przeglądu. Klasa MarsProperty ma też typ nieruchomości (do wynajęcia lub na sprzedaż) i cenę. Ekran szczegółów powinien zawierać obie te wartości. Warto też, aby w przypadku nieruchomości na wynajem było zaznaczone, że cena jest podana za miesiąc. Do obu tych czynności używasz przekształceń LiveData w modelu widoku.

  1. Otwórz pokój res/values/strings.xml. Kod początkowy zawiera zasoby ciągów tekstowych (widoczne poniżej), które pomogą Ci utworzyć ciągi tekstowe dla widoku szczegółów. W przypadku ceny użyjesz zasobu display_price_monthly_rental lub zasobu display_price w zależności od typu usługi.
<string name="type_rent">Rent</string>
<string name="type_sale">Sale</string>
<string name="display_type">For %s</string>
<string name="display_price_monthly_rental">$%,.0f/month</string>
<string name="display_price">$%,.0f</string>
  1. Otwórz pokój detail/DetailViewModel.kt. Na dole klasy dodaj kod widoczny poniżej.

    Zaimportuj androidx.lifecycle.Transformations, jeśli pojawi się prośba.

    Ta transformacja sprawdza, czy wybrana nieruchomość jest wynajmowana, przy użyciu tego samego testu co w pierwszym zadaniu. Jeśli usługa jest wynajmowana, przekształcenie wybiera odpowiedni ciąg znaków z zasobów za pomocą przełącznika Kotlin when {}. Oba te ciągi znaków muszą kończyć się liczbą, więc po nich dodaj property.price.
val displayPropertyPrice = Transformations.map(selectedProperty) {
   app.applicationContext.getString(
           when (it.isRental) {
               true -> R.string.display_price_monthly_rental
               false -> R.string.display_price
           }, it.price)
}
  1. Aby uzyskać dostęp do zasobów ciągów w projekcie, zaimportuj wygenerowaną klasę R.
import com.example.android.marsrealestate.R
  1. Po displayPropertyPrice przekształceniu dodaj kod widoczny poniżej. Ta transformacja łączy kilka zasobów tekstowych w zależności od tego, czy typem usługi jest wynajem.
val displayPropertyType = Transformations.map(selectedProperty) {
   app.applicationContext.getString(R.string.display_type,
           app.applicationContext.getString(
                   when (it.isRental) {
                       true -> R.string.type_rent
                       false -> R.string.type_sale
                   }))
}
  1. Otwórz pokój res/layout/fragment_detail.xml. Pozostała tylko jedna rzecz do zrobienia: powiązać nowe ciągi tekstowe (utworzone za pomocą LiveData przekształceń) z widokiem szczegółów. Aby to zrobić, ustaw wartość pola tekstowego dla tekstu typu nieruchomości na viewModel.displayPropertyType, a pola tekstowego dla tekstu wartości ceny na viewModel.displayPropertyPrice.
<TextView
   android:id="@+id/property_type_text"
...
android:text="@{viewModel.displayPropertyType}"
...
   tools:text="To Rent" />

<TextView
   android:id="@+id/price_value_text"
...
android:text="@{viewModel.displayPropertyPrice}"
...
   tools:text="$100,000" />
  1. Skompiluj i uruchom aplikację. Wszystkie dane usługi będą teraz wyświetlane na stronie szczegółów w odpowiednim formacie.

Projekt Android Studio: MarsRealEstateFinal

Wyrażenia wiążące

  • Używaj wyrażeń wiązania w plikach układu XML, aby wykonywać proste operacje programowe, takie jak obliczenia matematyczne lub testy warunkowe, na powiązanych danych.
  • Aby odwołać się do klas w pliku układu, użyj tagu <import> w tagu <data>.

Opcje zapytań do usługi internetowej

  • Żądania do usług internetowych mogą zawierać parametry opcjonalne.
  • Aby określić parametry zapytania w żądaniu, użyj adnotacji @QueryRetrofit.

Kurs Udacity:

Dokumentacja dla deweloperów aplikacji na Androida:

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

Do czego służy tag <import> w pliku układu XML?

▢ Dołączanie jednego pliku układu do drugiego.

▢ Umieść kod Kotlin w pliku układu.

▢ Zapewnij dostęp do usług powiązanych z danymi.

▢ umożliwiać odwoływanie się do klas i osób w nich uczestniczących w wyrażeniach wiążących;

Pytanie 2

Jak dodać opcję zapytania do wywołania usługi internetowej REST w Retrofit?

▢ Dołącz zapytanie na końcu adresu URL żądania.

▢ Dodaj do funkcji, która wysyła żądanie, parametr zapytania i oznacz go adnotacją @Query.

▢ Użyj klasy Query, aby utworzyć żądanie.

▢ W konstruktorze Retrofit użyj metody addQuery().

Rozpocznij kolejną lekcję: 9.1. Repozytorium

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