Основы Android Kotlin 08.3 Фильтрация и подробные представления с использованием интернет-данных

Эта практическая работа входит в курс «Основы Android Kotlin». Вы получите максимальную пользу от этого курса, если будете выполнять практические работы последовательно. Все практические работы курса перечислены на целевой странице практической работы «Основы Android Kotlin» .

Введение

В предыдущих практических работах к этому уроку вы узнали, как получать данные о недвижимости на Марсе из веб-сервиса и как создать RecyclerView с сеткой для загрузки и отображения изображений из этих данных. В этой лабораторной работе вы завершите работу над приложением MarsRealEstate, реализовав возможность фильтрации объектов недвижимости на Марсе по доступности для аренды или покупки. Вы также создадите подробное представление, чтобы при нажатии на фотографию объекта в обзоре пользователь видел подробное представление с подробной информацией об этом объекте.

Что вам уже следует знать

  • Как создавать и использовать фрагменты.
  • Как перемещаться между фрагментами и использовать Safe Args (плагин Gradle) для передачи данных между фрагментами.
  • Как использовать компоненты архитектуры, включая модели представлений, фабрики моделей представлений, преобразования и LiveData .
  • Как извлечь данные в формате JSON из веб-сервиса REST и преобразовать эти данные в объекты Kotlin с помощью библиотек Retrofit и Moshi .

Чему вы научитесь

  • Как использовать сложные выражения привязки в файлах макета.
  • Как делать запросы Retrofit к веб-сервису с параметрами запроса.

Что ты будешь делать?

  • Измените приложение MarsRealEstate, чтобы отметить объекты недвижимости Mars, выставленные на продажу (а не сдаваемые в аренду), значком доллара.
  • Используйте меню параметров на странице обзора, чтобы создать запрос веб-службы, который фильтрует свойства Mars по типу.
  • Создайте фрагмент сведений для объекта недвижимости Mars, подключите этот фрагмент к сетке обзора с помощью навигации и передайте данные о недвижимости в этот фрагмент.

В этой практической работе (и связанных с ней практических работах) вы работаете с приложением MarsRealEstate, которое показывает недвижимость, выставленную на продажу на Марсе. Это приложение подключается к интернет-серверу для извлечения и отображения данных о недвижимости, включая такие сведения, как цена и доступность недвижимости для продажи или аренды. Изображения каждого объекта недвижимости — это реальные фотографии с Марса, сделанные марсоходами NASA. В предыдущих практических работах вы создали RecyclerView с сеткой для всех фотографий недвижимости:

В этой версии приложения вы работаете с типом недвижимости (аренда или покупка) и добавляете значок в сетку, чтобы отметить объекты недвижимости, выставленные на продажу:

Вы можете изменить меню параметров приложения, чтобы отфильтровать сетку и показать только те объекты недвижимости, которые сдаются в аренду или продаются:

И наконец, вы создаете подробный вид для отдельного объекта недвижимости и подключаете значки на сетке обзора к этому фрагменту информации с помощью навигации:

До сих пор вы использовали только URL-адрес изображения объекта недвижимости. Однако данные об объекте недвижимости, которые вы определили в классе MarsProperty , также включают идентификатор, цену и тип (аренда или продажа). Чтобы освежить память, вот фрагмент JSON-данных, которые вы получаете от веб-сервиса:

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

В этом задании вы начнете работу с типом недвижимости «Марс», чтобы добавить изображение знака доллара к объектам недвижимости на странице обзора, которые выставлены на продажу.

Шаг 1: Обновите MarsProperty, включив тип

Класс MarsProperty определяет структуру данных для каждого свойства, предоставляемого веб-сервисом. В предыдущей лабораторной работе вы использовали библиотеку Moshi для анализа необработанного JSON-ответа веб-сервиса Mars и преобразования его в отдельные объекты данных MarsProperty .

На этом этапе вы добавляете логику в класс MarsProperty , чтобы указать, сдаётся ли недвижимость в аренду (то есть, является ли тип объекта строкой "rent" или "buy" ). Эта логика будет использоваться в нескольких местах, поэтому лучше добавить её здесь, в классе данных, чем дублировать.

  1. Откройте приложение MarsRealEstate из последней лабораторной работы. (Если у вас нет приложения, вы можете скачать MarsRealEstateGrid .)
  2. Откройте network/MarsProperty.kt . Добавьте тело к определению класса MarsProperty и добавьте пользовательский метод получения для isRental , который возвращает true если объект имеет тип "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"
}

Шаг 2: Обновите макет элемента сетки

Теперь обновите макет элемента для сетки изображений, чтобы отобразить знак доллара, который можно нарисовать только на тех изображениях недвижимости, которые выставлены на продажу:

Используя выражения привязки данных, вы можете выполнить этот тест полностью в XML-макете для элементов сетки.

  1. Откройте res/layout/grid_view_item.xml . Это файл макета для каждой отдельной ячейки сетки RecyclerView . В настоящее время файл содержит только элемент <ImageView> для изображения свойства.
  2. Внутри элемента <data> добавьте элемент <import> для класса View . Импорт используется, когда требуется использовать компоненты класса внутри выражения привязки данных в файле макета. В данном случае вы будете использовать константы View.GONE и View.VISIBLE , поэтому вам потребуется доступ к классу View .
<import type="android.view.View"/>
  1. Окружите все изображение элементом FrameLayout , чтобы можно было разместить значок доллара поверх изображения свойства.
<FrameLayout
   android:layout_width="match_parent"
   android:layout_height="170dp">
             <ImageView 
                    android:id="@+id/mars_image"
            ...
</FrameLayout>
  1. Для ImageView измените атрибут android:layout_height на match_parent , чтобы заполнить новый родительский FrameLayout .
android:layout_height="match_parent"
  1. Добавьте второй элемент <ImageView> непосредственно под первым, внутри FrameLayout . Используйте определение, показанное ниже. Это изображение отображается в правом нижнем углу элемента сетки, поверх изображения Марса, и использует объект drawable, определённый в res/drawable/ic_for_sale_outline.xml для значка доллара.
<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. Добавьте атрибут android:visibility к представлению изображения mars_property_type . Используйте выражение привязки для проверки типа недвижимости и назначьте видимость View.GONE (для аренды) или View.VISIBLE (для покупки).
 android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"

До сих пор вы видели выражения привязки только в макетах, использующих отдельные переменные, определённые в элементе <data> . Выражения привязки чрезвычайно эффективны и позволяют выполнять такие операции, как тесты и математические вычисления, полностью в макете XML. В данном случае вы используете тернарный оператор ( ?: ) для проверки (сдаётся ли этот объект в аренду?). Вы предоставляете один результат для значения true (скрыть значок доллара с помощью View.GONE ) и другой для значения false (отобразить этот значок с помощью View.VISIBLE ).

Новый полный файл grid_view_item.xml показан ниже:

<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. Скомпилируйте и запустите приложение. Обратите внимание, что объекты недвижимости, которые не сдаются в аренду, отмечены значком доллара.

В настоящее время ваше приложение отображает все объекты недвижимости на Марсе в сетке обзора. Если бы пользователь искал недвижимость для аренды на Марсе, значки, указывающие на то, какие из доступных объектов выставлены на продажу, были бы полезны, но на странице всё ещё много вариантов, которые приходится прокручивать. В этой задаче вы добавляете меню параметров к фрагменту обзора, которое позволяет пользователю отображать только арендуемые объекты, только объекты для продажи или все объекты.

Один из способов решения этой задачи — проверить тип каждого объекта MarsProperty в таблице обзора и отображать только соответствующие объекты. Однако в веб-сервисе Mars есть параметр запроса (называемый filter ), который позволяет получать только объекты типа rent или « buy . Вы можете использовать этот запрос с URL-адресом веб-сервиса realestate в браузере следующим образом:

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

В этой задаче вы измените класс MarsApiService , добавив параметр запроса к веб-сервису с помощью Retrofit. Затем вы подключите меню параметров для повторной загрузки всех данных о свойствах Mars с помощью этого параметра запроса. Поскольку ответ, получаемый от веб-сервиса, содержит только интересующие вас свойства, вам вообще не нужно менять логику отображения для обзорной сетки.

Шаг 1: Обновите службу API Mars

Чтобы изменить запрос, вам нужно вернуться к классу MarsApiService , который вы реализовали в первой лабораторной работе этой серии. Вы изменяете класс, чтобы предоставить API фильтрации.

  1. Откройте network/MarsApiService.kt . Чуть ниже импорта создайте enum MarsApiFilter , чтобы определить константы, соответствующие значениям запроса, ожидаемым веб-службой.
enum class MarsApiFilter(val value: String) {
   SHOW_RENT("rent"),
   SHOW_BUY("buy"),
   SHOW_ALL("all") }
  1. Измените метод getProperties() так, чтобы он принимал строковые входные данные для запроса фильтра, и аннотируйте эти входные данные с помощью @Query("filter") , как показано ниже.

    Импортируйте retrofit2.http.Query при появлении соответствующего запроса.

    Аннотация @Query указывает методу getProperties() (и, следовательно, Retrofit) выполнить запрос к веб-сервису с опцией фильтра. Каждый раз при вызове getProperties() URL-адрес запроса включает часть ?filter=type , которая предписывает веб-сервису предоставить результаты, соответствующие запросу.
fun getProperties(@Query("filter") type: String):  

Шаг 2: Обновите модель обзорного представления

Вы запрашиваете данные из MarsApiService в методе getMarsRealEstateProperties() в OverviewViewModel . Теперь вам нужно обновить этот запрос, чтобы он принимал аргумент фильтра.

  1. Откройте overview/OverviewViewModel.kt . Вы увидите ошибки в Android Studio из-за изменений, внесённых на предыдущем шаге. Добавьте MarsApiFilter (перечисление возможных значений фильтра) в качестве параметра к вызову getMarsRealEstateProperties() .

    Импортируйте com.example.android.marsrealestate.network.MarsApiFilter по запросу.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
  1. Измените вызов getProperties() в службе Retrofit, чтобы передать этот запрос фильтра как строку.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
  1. В блоке init {} передайте MarsApiFilter.SHOW_ALL в качестве аргумента для getMarsRealEstateProperties() , чтобы отобразить все свойства при первой загрузке приложения.
init {
   getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
  1. В конце класса добавьте метод updateFilter() , который принимает аргумент MarsApiFilter и вызывает getMarsRealEstateProperties() с этим аргументом.
fun updateFilter(filter: MarsApiFilter) {
   getMarsRealEstateProperties(filter)
}

Шаг 3: Подключите фрагмент к меню параметров.

Последний шаг — подключить дополнительное меню к фрагменту для вызова updateFilter() в модели представления, когда пользователь выбирает пункт меню.

  1. Откройте res/menu/overflow_menu.xml . В приложении MarsRealEstate есть дополнительное меню с тремя доступными вариантами: показывать все объекты недвижимости, показывать только объекты аренды и показывать только объекты недвижимости для продажи.
<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. Откройте overview/OverviewFragment.kt . В конце класса реализуйте метод onOptionsItemSelected() для обработки выбора пунктов меню.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. В методе onOptionsItemSelected() вызовите метод updateFilter() модели представления с соответствующим фильтром. Используйте блок Kotlin when {} для переключения между вариантами. Используйте MarsApiFilter.SHOW_ALL для значения фильтра по умолчанию. Возвращайте true , поскольку вы обработали пункт меню. Импортируйте MarsApiFilter ( com.example.android.marsrealestate.network.MarsApiFilter ) по запросу. Полный метод onOptionsItemSelected() показан ниже.
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. Скомпилируйте и запустите приложение. Приложение откроет первую обзорную таблицу со всеми типами недвижимости, включая выставленные на продажу объекты, отмеченные значком доллара.
  2. Выберите «Аренда» в меню параметров. Список объектов недвижимости перезагрузится, и ни один из них не будет отмечен значком доллара. (Отображаются только объекты, сдаваемые в аренду.) Возможно, вам придётся подождать несколько минут, пока экран обновится, чтобы отобразились только отфильтрованные объекты.
  3. Выберите «Купить» в меню параметров. Список объектов снова обновится, и все они будут отмечены значком доллара. (Отображаются только объекты, выставленные на продажу.)

Теперь у вас есть прокручиваемая сетка значков для объектов недвижимости на Марсе, но пора добавить больше информации. В этом задании вы добавите фрагмент для отображения информации о конкретном объекте. На фрагменте будет показано увеличенное изображение, цена и тип объекта — сдаётся ли он в аренду или продаётся.

Этот фрагмент запускается при нажатии пользователем на изображение в сетке обзора. Для этого необходимо добавить прослушиватель onClick к элементам сетки RecyclerView , а затем перейти к новому фрагменту. Навигация осуществляется путем активации изменения LiveData в ViewModel , как вы делали в этих уроках. Также используется плагин Safe Args компонента Navigation для передачи выбранной информации о MarsProperty из фрагмента обзора во фрагмент с подробным описанием.

Шаг 1: Создайте модель детального вида и обновите макет детали

Аналогично процессу, который вы использовали для модели обзора и фрагментов, теперь вам необходимо реализовать файлы модели представления и макета для фрагмента детали.

  1. Откройте detail/DetailViewModel.kt . Так же, как файлы Kotlin, связанные с сетью, находятся в папке network , а файлы Overview — в папке overview , папка detail содержит файлы, связанные с подробным представлением. Обратите внимание, что класс DetailViewModel (сейчас пустой) принимает marsProperty в качестве параметра в конструкторе.
class DetailViewModel( marsProperty: MarsProperty,
                     app: Application) : AndroidViewModel(app) {
}
  1. В определении класса добавьте LiveData для выбранного свойства Mars, чтобы отобразить эту информацию в подробном представлении. Следуйте стандартному шаблону: создайте MutableLiveData для хранения самого MarsProperty , а затем отобразите неизменяемое открытое свойство LiveData .

    Импортируйте androidx.lifecycle.LiveData и импортируйте androidx.lifecycle.MutableLiveData по запросу.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. Создайте блок init {} и задайте значение выбранного свойства Mars с помощью объекта MarsProperty из конструктора.
    init {
        _selectedProperty.value = marsProperty
    }
  1. Откройте res/layout/fragment_detail.xml и просмотрите его в режиме конструктора.

    Это файл макета фрагмента с подробностями. Он содержит ImageView для большой фотографии, TextView для типа недвижимости (аренда или продажа) и TextView для цены. Обратите внимание, что макет ограничений обрамлён ScrollView поэтому он автоматически прокручивается, если изображение становится слишком большим для экрана, например, при просмотре в альбомной ориентации.
  2. Перейдите на вкладку «Текст» макета. В верхней части макета, непосредственно перед элементом <ScrollView> , добавьте элемент <data> , чтобы связать модель подробного представления с макетом.
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
  1. Добавьте атрибут app:imageUrl к элементу ImageView . Задайте для него значение imgSrcUrl из свойства selected модели представления.

    Адаптер привязки, загружающий изображение с помощью Glide, будет здесь также использоваться автоматически, поскольку этот адаптер отслеживает все атрибуты app:imageUrl .
 app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"

Шаг 2: Определите навигацию в модели обзорного представления

Когда пользователь нажимает на фотографию в обзорной модели, должна запускаться навигация к фрагменту, содержащему подробную информацию о выбранном элементе.

  1. Откройте overview/OverviewViewModel.kt . Добавьте свойство MutableLiveData _navigateToSelectedProperty и предоставьте его с помощью неизменяемого LiveData .

    Когда значение LiveData изменяется на ненулевое, активируется навигация. (Вскоре вы добавите код для отслеживания этой переменной и активации навигации.)
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
   get() = _navigateToSelectedProperty
  1. В конце класса добавьте метод displayPropertyDetails() , который устанавливает _ navigateToSelectedProperty для выбранного свойства Mars.
fun displayPropertyDetails(marsProperty: MarsProperty) {
   _navigateToSelectedProperty.value = marsProperty
}
  1. Добавьте метод displayPropertyDetailsComplete() , который обнуляет значение _navigateToSelectedProperty . Это необходимо, чтобы отметить состояние навигации как завершённое и избежать повторного запуска навигации при возвращении пользователя из подробного представления.
fun displayPropertyDetailsComplete() {
   _navigateToSelectedProperty.value = null
}

Шаг 3: Настройте прослушиватели щелчков в адаптере сетки и фрагменте

  1. Откройте overview/PhotoGridAdapter.kt . В конце класса создайте пользовательский класс OnClickListener , принимающий лямбда-выражение с параметром marsProperty . Внутри класса определите функцию onClick() , которая будет использовать этот лямбда-параметр.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
     fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
  1. Прокрутите страницу до определения класса PhotoGridAdapter и добавьте частное свойство OnClickListener в конструктор.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. Сделайте фотографию кликабельной, добавив onClickListener к элементу сетки в методе onBindviewHolder() . Определите прослушиватель кликов между вызовами getItem() and bind() .
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
   val marsProperty = getItem(position)
   holder.itemView.setOnClickListener {
       onClickListener.onClick(marsProperty)
   }
   holder.bind(marsProperty)
}
  1. Откройте overview/OverviewFragment.kt . В методе onCreateView() замените строку, инициализирующую свойство binding.photosGrid.adapter , на строку, показанную ниже.

    Этот код добавляет объект PhotoGridAdapter.onClickListener в конструктор PhotoGridAdapter и вызывает viewModel.displayPropertyDetails() с переданным объектом MarsProperty . Это активирует LiveData в модели представления для навигации.
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
   viewModel.displayPropertyDetails(it)
})

Шаг 4: Измените навигационный граф и сделайте MarsProperty парцелляемым

Когда пользователь нажимает на фотографию в обзорной сетке, приложение должно перейти к фрагменту с подробностями и просмотреть сведения о выбранном объекте недвижимости на Марсе, чтобы подробное представление могло отобразить эту информацию.

Сейчас у вас есть прослушиватель щелчков из PhotoGridAdapter для обработки нажатия и способ активировать навигацию из модели представления. Но у вас пока нет объекта MarsProperty , передаваемого во фрагмент сведений. Для этого используются безопасные аргументы из компонента навигации.

  1. Откройте файл res/navigation/nav_graph.xml . Перейдите на вкладку «Текст» , чтобы просмотреть XML-код навигационного графика.
  2. Внутри элемента <fragment> фрагмента с подробными данными добавьте элемент <argument> , показанный ниже. Этот аргумент, называемый selectedProperty , имеет тип MarsProperty .
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. Скомпилируйте приложение. Навигация выдаёт ошибку, поскольку MarsProperty не является Parcelable . Интерфейс Parcelable позволяет сериализовать объекты, чтобы их данные могли передаваться между фрагментами или действиями. В этом случае, чтобы данные из объекта MarsProperty были переданы во фрагмент с подробными данными через безопасные аргументы, MarsProperty должен реализовать интерфейс Parcelable . Хорошая новость заключается в том, что Kotlin предоставляет простой способ реализации этого интерфейса.
  2. Откройте network/MarsProperty.kt и добавьте аннотацию @Parcelize к определению класса.

    Импортируйте kotlinx.android.parcel.Parcelize по запросу.

    Аннотация @Parcelize использует расширения Kotlin для Android для автоматической реализации методов интерфейса Parcelable для этого класса. Вам больше ничего делать не нужно!
@Parcelize
data class MarsProperty (
  1. Измените определение класса MarsProperty , чтобы расширить Parcelable .

    Импортируйте android.os.Parcelable по запросу.

    Определение класса MarsProperty теперь выглядит так:
@Parcelize
data class MarsProperty (
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double) : Parcelable {

Шаг 5: Соедините фрагменты

Навигация пока не реализована — сама навигация осуществляется во фрагментах. На этом этапе вы добавляете последние элементы для реализации навигации между обзорным и подробным фрагментами.

  1. Откройте overview/OverviewFragment.kt . В onCreateView() под строками, инициализирующими адаптер сетки фотографий, добавьте показанные ниже строки для наблюдения за navigatedToSelectedProperty из модели представления обзора.

    Импортируйте androidx.lifecycle.Observer и импортируйте androidx.navigation.fragment.findNavController по запросу.

    Наблюдатель проверяет, не равно ли MarsProperty ( it в лямбда-выражении) значению null, и если да, то получает контроллер навигации из фрагмента с помощью findNavController() . Вызовите displayPropertyDetailsComplete() , чтобы сообщить модели представления о необходимости сбросить LiveData в состояние null, чтобы случайно не запустить навигацию снова, когда приложение вернётся к OverviewFragment .
viewModel.navigateToSelectedProperty.observe(this, Observer {
   if ( null != it ) {   
      this.findNavController().navigate(
              OverviewFragmentDirections.actionShowDetail(it))             
      viewModel.displayPropertyDetailsComplete()
   }
})
  1. Откройте detail/DetailFragment.kt . Добавьте эту строку сразу после вызова setLifecycleOwner() в методе onCreateView() . Эта строка получает выбранный объект MarsProperty из безопасных аргументов.

    Обратите внимание на использование оператора проверки непустого значения Kotlin ( !! ). Если selectedProperty отсутствует, значит, произошло что-то ужасное, и вы хотите, чтобы код выдавал нулевой указатель. (В рабочем коде эту ошибку следует как-то обработать.)
 val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
  1. Добавьте следующую строку, чтобы получить новый DetailViewModelFactory . Вы будете использовать DetailViewModelFactory для получения экземпляра DetailViewModel . Стартовое приложение включает реализацию DetailViewModelFactory , поэтому вам остаётся только инициализировать её.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
  1. Наконец, добавьте эту строку, чтобы получить DetailViewModel с завода и соединить все детали.
      binding.viewModel = ViewModelProviders.of(
                this, viewModelFactory).get(DetailViewModel::class.java)
  1. Скомпилируйте и запустите приложение, затем нажмите на любую фотографию объекта недвижимости на Марсе. Появится фрагмент с подробной информацией об этом объекте. Нажмите кнопку «Назад», чтобы вернуться на страницу обзора, и обратите внимание, что экран с подробной информацией всё ещё довольно скудный. Вы завершите добавление данных об объекте на эту страницу в следующем задании.

Сейчас на странице сведений отображается только та же фотография Марса, которую вы привыкли видеть на странице обзора. Класс MarsProperty также определяет тип недвижимости (аренда или покупка) и её цену. На странице сведений должны отображаться оба этих значения, и было бы полезно, если бы для объектов аренды указывалась цена в месяц. Для реализации обоих этих функций используются преобразования LiveData в модели представления.

  1. Откройте файл res/values/strings.xml . Стартовый код включает строковые ресурсы, показанные ниже, которые помогут вам создать строки для подробного представления. Для отображения цены используйте ресурс display_price_monthly_rental или display_price , в зависимости от типа недвижимости.
<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. Откройте detail/DetailViewModel.kt . Внизу класса добавьте код, показанный ниже.

    При необходимости импортируйте androidx.lifecycle.Transformations .

    Это преобразование проверяет, является ли выбранная недвижимость сдаваемой в аренду, используя тот же тест из первой задачи. Если недвижимость сдаётся в аренду, преобразование выбирает соответствующую строку из ресурсов с помощью переключателя Kotlin when {} . Обе эти строки должны иметь число в конце, поэтому после этого вы добавляете 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. Импортируйте сгенерированный класс R , чтобы получить доступ к строковым ресурсам в проекте.
import com.example.android.marsrealestate.R
  1. После преобразования displayPropertyPrice добавьте код, показанный ниже. Это преобразование объединяет несколько строковых ресурсов в зависимости от того, является ли недвижимость арендной.
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. Откройте res/layout/fragment_detail.xml . Осталось только привязать новые строки (созданную с помощью преобразований LiveData ) к подробному представлению. Для этого установите значение текстового поля для типа свойства text равным viewModel.displayPropertyType , а значение цены text равным 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. Скомпилируйте и запустите приложение. Теперь все данные о недвижимости отображаются на странице сведений в удобном формате.

Проект Android Studio: MarsRealEstateFinal

Связывающие выражения

  • Используйте выражения привязки в файлах макета XML для выполнения простых программных операций, таких как математические или условные проверки, над связанными данными.
  • Для ссылки на классы внутри файла макета используйте тег <import> внутри тега <data> .

Параметры запроса веб-сервиса

  • Запросы к веб-сервисам могут включать необязательные параметры.
  • Чтобы указать параметры запроса в запросе, используйте аннотацию @Query в Retrofit .

Курс Udacity:

Документация для разработчиков Android:

Другой:

В этом разделе перечислены возможные домашние задания для студентов, работающих над этой лабораторной работой в рамках курса, проводимого преподавателем. Преподаватель должен выполнить следующие действия:

  • При необходимости задавайте домашнее задание.
  • Объясните учащимся, как следует сдавать домашние задания.
  • Оцените домашние задания.

Преподаватели могут использовать эти предложения так часто или редко, как пожелают, и могут свободно задавать любые другие домашние задания, которые они сочтут подходящими.

Если вы работаете с этой лабораторной работой самостоятельно, можете использовать эти домашние задания для проверки своих знаний.

Ответьте на эти вопросы

Вопрос 1

Что делает тег <import> в файле макета XML?

▢ Включить один файл макета в другой.

▢ Встроить код Kotlin в файл макета.

▢ Предоставлять доступ к свойствам, привязанным к данным.

▢ Позволяет ссылаться на классы и члены классов в выражениях привязки.

Вопрос 2

Как добавить параметр запроса в вызов веб-службы REST в Retrofit?

▢ Добавьте запрос в конец URL-адреса запроса.

▢ Добавьте параметр для запроса в функцию, которая выполняет запрос, и аннотируйте этот параметр с помощью @Query .

▢ Используйте класс Query для создания запроса.

▢ Используйте метод addQuery() в конструкторе Retrofit.

Начните следующий урок: 9.1: Репозиторий

Ссылки на другие практические занятия по этому курсу см. на целевой странице практических занятий по основам Android Kotlin .