Android Kotlin Fundamentals 08.3 Filtrowanie i widoki szczegółów z danymi z internetu

Te ćwiczenia są częścią kursu Android Kotlin Fundamentals. Skorzystaj z tego kursu, jeśli będziesz wykonywać kolejno kilka ćwiczeń z programowania. Wszystkie ćwiczenia z kursu są wymienione na stronie docelowej ćwiczeń z programowania na temat Kotlin.

Wprowadzenie

Na tych lekcjach z tej lekcji dowiesz się, jak pobierać dane o nieruchomościach na Marsie z usługi internetowej oraz jak utworzyć RecyclerView z układem siatki do wczytywania i wyświetlania obrazów z tych danych. Dzięki temu ćwiczeniu możesz ukończyć aplikację MarsRealEstate, implementując możliwość filtrowania właściwości Marsa według ich dostępności lub wynajmu. Możesz też utworzyć widok szczegółowy, więc jeśli użytkownik kliknie zdjęcie usługi w widoku ogólnym, zobaczy widok szczegółowy z informacjami o danej usłudze.

Co warto wiedzieć

  • Jak tworzyć fragmenty i ich używać.
  • Poruszanie się po fragmentach i używanie Safe Args (wtyczki Gradle) do przekazywania danych między fragmentami.
  • Jak używać komponentów architektury, takich jak modele widoku danych, fabryki modeli, przekształcenia i LiveData.
  • Jak pobrać dane zakodowane w formacie JSON z usługi internetowej REST i przeanalizować je w obiektach Kotlin za pomocą bibliotek Retrofit i Moshi.

Czego się nauczysz:

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

Co chcesz zrobić

  • Zmodyfikuj aplikację MarsRealEstate, aby oznaczyć nieruchomość na Marsie, która jest na sprzedaż (a nie, czy na wynajem), z ikoną dolara.
  • Użyj menu opcji na stronie przeglądu, aby utworzyć żądanie usługi internetowej, które będzie filtrować usługi Mars według typu.
  • Utwórz fragment szczegółów dla właściwości Marsa, połącz go z siatką Przegląd z nawigacją i przekaż do niego dane usługi.

To ćwiczenie programowania (i powiązane z nimi ćwiczenia) obejmuje aplikację MarsRealEstate, która wyświetla usługi na Marsie. Ta aplikacja łączy się z serwerem internetowym, aby pobierać i wyświetlać dane o nieruchomościach, m.in. o ich cenie oraz o tym, czy nieruchomości jest na sprzedaż lub do wynajęcia. Zdjęcia przedstawiające każdą nieruchomość to rzeczywiste zdjęcia Marsa zrobione przez marsjańskie łaziki. W poprzednich ćwiczeniach z programowania utworzono RecyclerView z układem siatki dla wszystkich zdjęć usługi.

W tej wersji aplikacji korzystasz z typu usługi (wynajem lub kupowanie) i dodajesz ikonę do układu siatki, aby oznaczyć usługi na sprzedaż:

Menu aplikacji możesz zmodyfikować, aby wyświetlić tylko te usługi, które są przeznaczone do wynajęcia lub na sprzedaż:

Na koniec tworzysz widok szczegółowy dla pojedynczej usługi i łączysz ikony w siatce przeglądu z tym fragmentem z nawigacją:

Do tej pory jedynym elementem danych Marsa jest adres URL obrazu właściwości. Jednak dane usługi określone w klasie MarsProperty zawierają też identyfikator, cenę oraz typ (wynajem lub sprzedaż). Aby odświeżyć pamięć, oto fragment kodu JSON, który otrzymasz 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 właściwością Marsa, aby dodać obraz właściwości z symbolem dolara do właściwości na stronie przeglądu, które są na sprzedaż.

Krok 1. Zaktualizuj typ właściwości MarsProperty, aby uwzględnić typ

Klasa MarsProperty określa strukturę danych dla każdej usługi dostarczanej przez usługę internetową. W poprzednim ćwiczeniu z programowania użyto biblioteki Moshi do przeanalizowania nieprzetworzonej odpowiedzi JSON z usługi internetowej Marsa na potrzeby poszczególnych obiektów danych MarsProperty.

W tym kroku dodasz logikę do klasy MarsProperty, aby wskazać, czy usługa jest do wynajęcia (czyli czy jest to ciąg znaków "rent", czy "buy"). Logika będzie używana w więcej niż jednym miejscu, dlatego lepiej jest tu umieścić ją w klasie danych niż do jej odtworzenia.

  1. Otwórz aplikację MarsRealEstate z ostatniego ćwiczenia z programowania. (Jeśli nie masz aplikacji, możesz pobrać aplikację MarsRealEstateGrid).
  2. Otwórz aplikację network/MarsProperty.kt. Dodaj treść do definicji klasy MarsProperty i dodaj niestandardowy obiekt getter 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 musisz zmienić układ elementu na siatce obrazów, aby wyświetlać znak dolara rysowany tylko w przypadku tych zdjęć nieruchomości, które są na sprzedaż:

Wyrażenia wiązania danych możesz przeprowadzić w całości w układzie XML dla elementów siatki.

  1. Otwórz aplikację res/layout/grid_view_item.xml. To jest plik układu każdej komórki w układzie siatki dla RecyclerView. Obecnie plik zawiera tylko element <ImageView> obrazu usługi.
  2. W elemencie <data> dodaj element <import> klasy View. Importów używasz, gdy chcesz użyć komponentów klasy w wyrażeniu wyrażeń danych w pliku układu. W tym przypadku będziesz używać stałych View.GONE i View.VISIBLE, dlatego potrzebujesz dostępu do klasy View.
<import type="android.view.View"/>
  1. Obrysuj cały widok obrazu za pomocą operatora FrameLayout, by umożliwić nakładanie znaku dolara na obraz usługi.
<FrameLayout
   android:layout_width="match_parent"
   android:layout_height="170dp">
             <ImageView 
                    android:id="@+id/mars_image"
            ...
</FrameLayout>
  1. W polu 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, wewnątrz elementu FrameLayout. Użyj definicji podanej poniżej. Obraz wyświetla się w prawym dolnym rogu elementu siatki (na górze obrazu Marsa) i jest kreślony na podstawie ikony res/drawable/ic_for_sale_outline.xml 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 zdjęcia mars_property_type. Użyj wyrażenia wiążącego, by przetestować ten typ usługi, i przypisz widoczność do View.GONE (w przypadku wypożyczenia) lub View.VISIBLE (w przypadku zakupu).
 android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"

Do tej pory w układach, które korzystają z pojedynczych zmiennych zdefiniowanych w elemencie <data>, były używane tylko wyrażenia wiązania. Wyrażenia wiążące są bardzo zaawansowane i umożliwiają wykonywanie takich operacji jak testy i obliczenia matematyczne w całości w układzie XML. W tym przypadku do przeprowadzenia testu użyjesz operatora potrójnego (?:) (czy ten obiekt jest wypożyczony?). Podaj jeden wynik dla prawdy (ukryj ikonę dolara z View.GONE) i drugi fałsz (wyświetl tę ikonę z View.VISIBLE).

Nowy, kompletny plik grid_view_item.xml jest wyświetlany 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ę. Pamiętaj, że usługi, które nie są wypożyczalniami, mają ikonę dolara.

Obecnie Twoja aplikacja wyświetla wszystkie właściwości Marsa w siatce przeglądu. Jeśli użytkownik chce wynająć mieszkanie na Marsie, ikony informujące, które z nich są na sprzedaż, mogą być przydatne, ale na stronie nadal może się pojawiać wiele usług. W tym zadaniu dodasz menu opcji do fragmentu przeglądu, który umożliwia użytkownikowi wyświetlenie tylko wynajmu, tylko nieruchomości na sprzedaż lub wyświetlanie wszystkich ofert.

Możesz na przykład przetestować typ każdej MarsProperty w siatce przeglądu i wyświetlić tylko odpowiadające jej właściwości. Rzeczywista usługa internetowa Mars ma jednak parametr zapytania lub opcję (o nazwie filter), która umożliwia uzyskanie tylko właściwości typu rent lub buy. Możesz użyć tego filtra z adresem URL usługi internetowej realestate w przeglądarce:

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

W tym zadaniu zmodyfikujesz klasę MarsApiService, aby dodać opcję zapytania do usługi internetowej za pomocą Retrofit. Następnie podłącz menu opcji, by ponownie pobrać wszystkie dane właściwości Marsa przy użyciu tej opcji zapytania. Odpowiedź otrzymana z usługi internetowej zawiera tylko te usługi, które Cię interesują, więc w ogóle nie musisz zmieniać logiki wyświetlania widoku siatki.

Krok 1. Zaktualizuj usługę Mars API

Aby zmienić tę prośbę, musisz jeszcze raz obejrzeć klasę MarsApiService zaimplementowaną w pierwszym ćwiczeniu z programowania w tej serii. Zmodyfikuj klasę, aby udostępnić interfejs API filtrowania.

  1. Otwórz aplikację network/MarsApiService.kt. Tuż pod importem utwórz enum o nazwie MarsApiFilter, by zdefiniować stałe wartości odpowiadające zapytaniu oczekiwanej przez usługę internetową.
enum class MarsApiFilter(val value: String) {
   SHOW_RENT("rent"),
   SHOW_BUY("buy"),
   SHOW_ALL("all") }
  1. Zmodyfikuj metodę getProperties() tak, by pobierała dane wejściowe ciągu zapytania dla filtra. Dodaj do nich też adnotacje @Query("filter"), jak pokazano poniżej.

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

    Adnotacja @Query informuje metodę getProperties() (a tym samym Retrofit) o żądanie usługi internetowej za pomocą opcji filtrowania. Przy każdym wywołaniu getProperties() adres URL żądania zawiera część ?filter=type, która kieruje usługę internetową, aby odpowiedziała na wyniki pasujące do tego zapytania.
fun getProperties(@Query("filter") type: String):  

Krok 2. Zaktualizuj model widoku przeglądu

Żądasz danych z metody MarsApiService w metodzie getMarsRealEstateProperties() w OverviewViewModel. Teraz musisz zaktualizować to żądanie, by przyjąć argument filtra.

  1. Otwórz aplikację overview/OverviewViewModel.kt. Z powodu zmian wprowadzonych w poprzednim kroku zobaczysz w Android Studio błędy. Dodaj MarsApiFilter (wyliczenie możliwych wartości filtra) jako parametr do wywołania getMarsRealEstateProperties().

    Zaimportuj com.example.android.marsrealestate.network.MarsApiFilter na żądanie.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
  1. Zmodyfikuj wywołanie funkcji getProperties() w usłudze Retrofit, aby przekazywać to zapytanie filtra w postaci ciągu 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 właściwości przy pierwszym załadowaniu aplikacji.
init {
   getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
  1. Na końcu klasy dodaj metodę updateFilter(), która przyjmuje argument MarsApiFilter i wywołuje za jego pomocą parametr getMarsRealEstateProperties().
fun updateFilter(filter: MarsApiFilter) {
   getMarsRealEstateProperties(filter)
}

Krok 3. Połącz fragment z menu opcji

Ostatnim krokiem jest powiązanie rozszerzonego menu z fragmentem w celu wywołania metody updateFilter() w modelu widoku danych, gdy użytkownik wybierze opcję menu.

  1. Otwórz aplikację res/menu/overflow_menu.xml. W aplikacji MarsRealEstate jest dostępne rozszerzone menu, które zawiera trzy opcje: wyświetlanie wszystkich nieruchomości, tylko wypożyczanie 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 aplikację overview/OverviewFragment.kt. Na końcu klasy zaimplementuj metodę onOptionsItemSelected(), która obsługuje wybrany element menu.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. W onOptionsItemSelected() musisz wywołać metodę updateFilter() w modelu widoku danych z odpowiednim filtrem. Aby przełączać się między opcjami, użyj bloku when {} Kotlin. Użyj MarsApiFilter.SHOW_ALL jako domyślnej wartości filtra. Zwrócono true, ponieważ to pozycji menu. Importuj MarsApiFilter (com.example.android.marsrealestate.network.MarsApiFilter) na żądanie. Pełna metoda onOptionsItemSelected() jest widoczna 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ądu ze wszystkimi typami usług i nieruchomościami na sprzedaż oznaczonymi ikoną dolara.
  2. W menu „Opcje” wybierz Wypożycz. Właściwości ponownie wczytają się i żadna z nich nie wyświetli się z ikoną dolara. Wyświetlane są tylko nieruchomości na wynajem. Aby wyniki zostały odświeżone, może minąć kilka chwil, aż ekran się odświeży.
  3. W menu „Opcje” kliknij Kup. Właściwości zostaną ponownie załadowane, a wszystkie pojawią się z ikoną dolara. Wyświetlane są tylko nieruchomości na sprzedaż.

Teraz masz do dyspozycji przewijaną siatkę ikon dla usług Marsa, ale czas na szczegóły. W tym zadaniu dodasz fragment szczegółów, aby wyświetlić szczegóły konkretnej właściwości. Fragment szczegółów wyświetli większy obraz, cenę i typ nieruchomości – np. wypożyczalnię lub sprzedaż.

Ten fragment jest uruchamiany, gdy użytkownik kliknie obraz na siatce przeglądu. Aby to zrobić, musisz dodać detektor onClick do elementów siatki RecyclerView, a potem przejść do nowego fragmentu. Poruszasz się, wywołując zmianę LiveData w ViewModel tak samo jak w czasie tych lekcji. Można też korzystać z wtyczki Safe Args do nawigacji w celu przekazywania wybranych informacji MarsProperty z fragmentu omówienia do fragmentu szczegółów.

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

Podobnie jak w przypadku modelu z widokiem przeglądu i fragmentów, musisz teraz zaimplementować pliki z modelem widoku i układem na potrzeby fragmentu szczegółów.

  1. Otwórz aplikację detail/DetailViewModel.kt. Pliki związane z siecią Kotlin znajdują się w folderze network, a pliki przeglądu w folderze overview – w folderze detail znajdują się pliki powiązane z widokiem szczegółowym. Zauważ, że klasa DetailViewModel (w tej chwili pusta) zawiera parametr marsProperty w parametrze konstruktora.
class DetailViewModel( marsProperty: MarsProperty,
                     app: Application) : AndroidViewModel(app) {
}
  1. Aby wyświetlić te informacje w widoku szczegółów, dodaj do definicji klasy Mars LiveData. Postępuj zgodnie ze zwykłym wzorcem tworzenia elementu MutableLiveData, by przechowywać właściwość MarsProperty, a następnie udostępnić niezmienną publiczną właściwość LiveData.

    Importuj (androidx.lifecycle.LiveData) i importuj (androidx.lifecycle.MutableLiveData) na żądanie.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. Utwórz blok init {} i ustaw wartość wybranej właściwości Marsa za pomocą obiektu MarsProperty z konstruktora.
    init {
        _selectedProperty.value = marsProperty
    }
  1. Otwórz folder res/layout/fragment_detail.xml i przyjrzyj się mu w widoku projektu.

    Jest to plik układu fragmentu szczegółów. Zawiera ImageView dla dużego zdjęcia, TextView dla typu nieruchomości (wynajem lub sprzedaż) oraz TextView dla ceny. Zwróć uwagę, że układ ograniczenia jest opakowany za pomocą ScrollView, więc będzie się automatycznie przewijać, gdy widok będzie za duży dla wyświetlacza, na przykład gdy użytkownik wyświetli go w trybie poziomym.
  2. Otwórz kartę Tekst układu. U góry układu tuż przed elementem <ScrollView> dodaj element <data>, by powiązać z nim układ widoku szczegółów.
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
  1. Dodaj atrybut app:imageUrl do elementu ImageView. Ustaw w nim wartość imgSrcUrl z wybranej właściwości widoku.

    Używany jest tu też adapter wiążący, który wczytuje obraz za pomocą gestu, ponieważ sprawdza on wszystkie atrybuty app:imageUrl.
 app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"

Krok 2. Zdefiniuj nawigację w modelu widoku przeglądu

Gdy użytkownik kliknie zdjęcie w modelu przeglądu, powinno to spowodować przejście do fragmentu zawierającego szczegółowe informacje o klikniętym elemencie.

  1. Otwórz aplikację overview/OverviewViewModel.kt. Dodaj właściwość _navigateToSelectedProperty MutableLiveData i udostępnij ją stałemu LiveData.

    Gdy ten LiveData zmieni się na inną niż null, nawigacja zostanie aktywowana. Wkrótce dodasz kod, by obserwować tę zmienną i uruchomić nawigację.
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
   get() = _navigateToSelectedProperty
  1. Na koniec klasy dodaj metodę displayPropertyDetails(), która ustawia _navigateToSelectedProperty do wybranej właściwości Marsa.
fun displayPropertyDetails(marsProperty: MarsProperty) {
   _navigateToSelectedProperty.value = marsProperty
}
  1. Dodaj metodę displayPropertyDetailsComplete(), która ma wartość _navigateToSelectedProperty. Musisz to zaznaczyć, aby oznaczyć stan nawigacji jako zakończony i nie uruchamiać się ponownie po wyświetleniu użytkownikowi szczegółów.
fun displayPropertyDetailsComplete() {
   _navigateToSelectedProperty.value = null
}

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

  1. Otwórz aplikację overview/PhotoGridAdapter.kt. Na końcu klasy utwórz niestandardową klasę OnClickListener, która przyjmuje lambdę za pomocą parametru marsProperty. W klasie zdefiniuj funkcję onClick() ustawioną na parametr lambda.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
     fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
  1. Przewiń w górę do definicji klasy obiektu PhotoGridAdapter i dodaj do konstruktora prywatną właściwość OnClickListener.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. Aby umożliwić kliknięcie zdjęcia, dodaj onClickListener do elementu siatki w metodzie onBindviewHolder(). Określ detektor kliknięć między wywołaniami 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 aplikację overview/OverviewFragment.kt. W metodzie onCreateView() zastąp wiersz inicjujący właściwość binding.photosGrid.adapter linią podaną poniżej.

    Ten kod dodaje obiekt PhotoGridAdapter.onClickListener do konstruktora PhotoGridAdapter i wywołuje element viewModel.displayPropertyDetails() z przekazanym obiektem MarsProperty. Powoduje to wyświetlenie LiveData w modelu widoku danych nawigacji.
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
   viewModel.displayPropertyDetails(it)
})

Krok 4. Zmodyfikuj wykres nawigacyjny i włącz funkcję MarsProperty (pakiet)

Gdy użytkownik kliknie zdjęcie w siatce przeglądu, aplikacja powinna przejść do fragmentu szczegółów i przejść szczegóły wybranej usługi Marsa w celu wyświetlenia tych informacji w widoku szczegółowym.

Obecnie masz detektor kliknięć z usługi PhotoGridAdapter, który obsługuje kliknięcie, oraz umożliwia uruchomienie nawigacji z modelu widoku. Nie masz jeszcze obiektu MarsProperty przekazywanego do fragmentu szczegółów. Aby to zrobić, użyj Safe Args z komponentu nawigacyjnego.

  1. Otwórz aplikację res/navigation/nav_graph.xml. Kliknij kartę Tekst, aby wyświetlić kod XML wykresu nawigacyjnego.
  2. W elemencie <fragment> fragmentu fragmentu dodaj element <argument> pokazany poniżej. Ten argument o nazwie selectedProperty ma typ MarsProperty.
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. Skompiluj aplikację. Nawigacja wyświetli błąd, ponieważ MarsProperty nie jest parcelable. Interfejs Parcelable umożliwia serializację obiektów, co pozwala na przesyłanie danych między fragmentami lub aktywnościami. W takim przypadku, aby dane w obiekcie MarsProperty zostały przekazane do fragmentu szczegółów za pomocą Safe Args, MarsProperty musi zaimplementować interfejs Parcelable. Dobra wiadomość jest taka, że Kotlin udostępnia łatwy skrót do implementacji tego interfejsu.
  2. Otwórz aplikację network/MarsProperty.kt. Dodaj adnotację @Parcelize do definicji klasy.

    Importuj kotlinx.android.parcel.Parcelize, jeśli jest taka potrzeba.

    Adnotacja @Parcelize używa rozszerzeń Androida Kotlin do automatycznego implementowania metod w interfejsie Parcelable dla tych zajęć. Nie musisz nic więcej robić.
@Parcelize
data class MarsProperty (
  1. Zmień definicję klasy MarsProperty, aby przedłużyć Parcelable.

    Importuj android.os.Parcelable, jeśli pojawi się taka prośba.

    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 korzystasz z nawigacji – rzeczywista nawigacja ma miejsce we fragmentach. W tym kroku dodasz ostatnie bity służące do implementacji nawigacji między fragmentami przeglądu i szczegółami.

  1. Otwórz aplikację overview/OverviewFragment.kt. W onCreateView() pod liniami, które inicjują adapter siatki zdjęć, dodaj widoczne poniżej linie, aby obserwować navigatedToSelectedProperty z modelu widoku przeglądu.

    Importuj (androidx.lifecycle.Observer) i importuj (androidx.navigation.fragment.findNavController) na żądanie.

    Obserwator sprawdza, czy MarsPropertyit w lambdzie – nie jest pusty. Jeśli tak, pobiera kontroler nawigacyjny z fragmentu za pomocą findNavController(). Wywołaj displayPropertyDetailsComplete(), by poprosić o zresetowanie modelu LiveData i ustawienie go do wartości null. Dzięki temu nie musisz przypadkowo uruchamiać ponownie nawigacji, gdy aplikacja wraca do OverviewFragment.
viewModel.navigateToSelectedProperty.observe(this, Observer {
   if ( null != it ) {   
      this.findNavController().navigate(
              OverviewFragmentDirections.actionShowDetail(it))             
      viewModel.displayPropertyDetailsComplete()
   }
})
  1. Otwórz aplikację detail/DetailFragment.kt. Dodaj ten wiersz pod wywołaniem setLifecycleOwner() w metodzie onCreateView(). Ten wiersz pobiera wybrany obiekt MarsProperty z Safe Args.

    Zwróć uwagę na użycie niepodlegającego pustego operatora Kotlin (!!). Jeśli parametr selectedProperty nie jest dostępny, wystąpiła tragiczna sytuacja. Chcesz umieścić kod pusty. (W kodzie produkcyjnym należy to zrobić w jakiś sposób).
 val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
  1. Dodaj ten wiersz teraz, aby otrzymać nową kolumnę DetailViewModelFactory. Aby uzyskać instancję DetailViewModel, użyj DetailViewModelFactory. Aplikacja startowa zawiera implementację tagu DetailViewModelFactory, więc wystarczy tylko go zainicjować.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
  1. Dodaj ten wiersz, aby pobrać 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 następnie kliknij dowolne zdjęcie marsjańskie właściwości. Pojawi się fragment szczegółów tej właściwości. Kliknij przycisk Wstecz, aby wrócić na stronę przeglądu. Pamiętaj, że ekran szczegółów jest nadal mało widoczny. Dane usługi możesz dodać do tej strony szczegółów w następnym zadaniu.

Obecnie na stronie szczegółów widać tylko to samo zdjęcie Marsa, które znasz na stronie przeglądu. Klasa MarsProperty zawiera też typ właściwości (wynajem lub zakup) oraz cenę usługi. Ekran szczegółów powinien zawierać obie te wartości. Pomocne będzie, jeśli właściwości związane z wypożyczeniem wskazują, że cena dotyczyła płatności za miesiąc. Aby wdrożyć te obie funkcje, użyj przekształceń typu LiveData w widoku danych.

  1. Otwórz aplikację res/values/strings.xml. Kod początkowy zawiera poniższe ciągi tekstowe, które ułatwiają tworzenie ciągów znaków na potrzeby widoku szczegółów. W przypadku tej ceny będziesz używać 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 aplikację detail/DetailViewModel.kt. U dołu zajęć dodaj kod widoczny poniżej.

    Jeśli chcesz, zaimportuj androidx.lifecycle.Transformations.

    Ta transformacja sprawdza, czy wybrana usługa jest wynajemem, korzystając z tego samego testu z pierwszego zadania. Jeśli usługa jest wynajmem, przekształcenie wybiera odpowiedni ciąg z zasobów za pomocą przełącznika when {} w Kotlin. Oba te ciągi wymagają liczby na końcu, dlatego musisz połączyć 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. Zaimportuj wygenerowaną klasę R, aby uzyskać dostęp do zasobów ciągu znaków w projekcie.
import com.example.android.marsrealestate.R
  1. Po przekształceniu displayPropertyPrice dodaj poniższy kod. Ta transformacja łączy wiele zasobów ciągów, w zależności od tego, czy typ 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 aplikację res/layout/fragment_detail.xml. Trzeba tylko zrobić jeszcze jedną rzecz, czyli powiązać nowe ciągi (utworzone za pomocą przekształceń LiveData) z widokiem szczegółów. Aby to zrobić, ustaw wartość w polu tekstowym tekstu typu właściwości na viewModel.displayPropertyType, a w polu tekstowym Wartość ceny – 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ę. Teraz wszystkie dane usługi są dobrze widoczne na stronie z informacjami.

Projekt na Android Studio: MarsRealEstateFinal

Powiązane wyrażenia

  • W plikach układu XML możesz używać wyrażeń, aby wykonywać proste operacje zautomatyzowane, takie jak działania matematyczne czy warunkowe w danych powiązanych.
  • Aby odwołać się do zajęć w pliku układu, użyj tagu <import> w tagu <data>.

Opcje zapytań dotyczących usług internetowych

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

Kurs Udacity:

Dokumentacja dla programistów Androida:

Inne:

Ta sekcja zawiera listę możliwych zadań domowych dla uczniów, którzy pracują w ramach tego ćwiczenia w ramach kursu prowadzonego przez nauczyciela. To nauczyciel może wykonać te czynności:

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

Nauczyciele mogą wykorzystać te sugestie tak długo, jak chcą lub chcą, i mogą przypisać dowolne zadanie domowe.

Jeśli samodzielnie wykonujesz te ćwiczenia z programowania, możesz sprawdzić swoją wiedzę w tych zadaniach domowych.

Odpowiedz na te pytania

Pytanie 1

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

▢ Uwzględnij jeden plik z układem w drugim.

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

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

▢ Umożliwia odwoływanie się do zajęć i ich członków 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 parametr zapytania do funkcji, która wysyła żądanie, i dodaj adnotacje do parametru @Query.

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

▢ Użyj metody addQuery() w narzędziu do tworzenia Retrofit.

Rozpocznij następną lekcję: 9.1 Repozytorium

Linki do innych ćwiczeń z programowania w tym kursie znajdziesz na stronie docelowej z ćwiczeniami z podstaw Androida Kotlin.