Эта кодовая лаборатория является частью курса Android Kotlin Fundamentals. Вы получите максимальную отдачу от этого курса, если будете последовательно работать с лабораториями кода. Все кодовые лаборатории курса перечислены на целевой странице кодовых лабораторий Android Kotlin Fundamentals .
Введение
В предыдущих лабораторных работах для этого урока вы узнали, как получить данные о недвижимости на Марсе из веб-службы и как создать RecyclerView
с макетом сетки для загрузки и отображения изображений из этих данных. В этой лабораторной работе вы завершаете работу над приложением MarsRealEstate, реализуя возможность фильтрации свойств Mars по тому, доступны ли они для аренды или покупки. Вы также создаете подробное представление, чтобы, если пользователь коснется фотографии свойства в обзоре, он увидел подробное представление с подробными сведениями об этом свойстве.
Что вы уже должны знать
- Как создавать и использовать фрагменты.
- Как перемещаться между фрагментами и использовать Safe Args (плагин Gradle) для передачи данных между фрагментами.
- Как использовать компоненты архитектуры, включая модели представлений, фабрики моделей представлений, преобразования и
LiveData
. - Как получить закодированные данные JSON из веб-службы REST и проанализировать эти данные в объектах Kotlin с помощью библиотек Retrofit и Moshi .
Что вы узнаете
- Как использовать сложные выражения привязки в ваших файлах макетов.
- Как сделать запросы на доработку к веб-службе с параметрами запроса.
Что ты будешь делать
- Измените приложение MarsRealEstate, чтобы пометить недвижимость Mars, выставленную на продажу (а не сдаваемую в аренду), значком доллара.
- Используйте меню параметров на странице обзора, чтобы создать запрос веб-службы, фильтрующий свойства Mars по типу.
- Создайте фрагмент сведений для свойства Mars, подключите этот фрагмент к сетке обзора с помощью навигации и передайте данные свойства в этот фрагмент.
В этой лаборатории кода (и связанных с ней лабораториях кода) вы работаете с приложением MarsRealEstate, которое показывает недвижимость, выставленную на продажу на Марсе. Это приложение подключается к интернет-серверу для получения и отображения данных о недвижимости, включая такие сведения, как цена и доступность недвижимости для продажи или аренды. Изображения, представляющие каждое свойство, представляют собой реальные фотографии с Марса, сделанные марсоходами НАСА. В предыдущих лабораториях кода вы создали RecyclerView
с макетом сетки для всех фотографий собственности:
В этой версии приложения вы работаете с типом недвижимости (аренда или покупка) и добавляете значок в макет сетки, чтобы помечать объекты, выставленные на продажу:
Вы изменяете меню параметров приложения, чтобы отфильтровать сетку, чтобы отображались только те свойства, которые сдаются в аренду или продаются:
И, наконец, вы создаете подробное представление для отдельного свойства и соединяете значки в обзорной сетке с этим фрагментом сведений с помощью навигации:
До сих пор единственной частью данных свойства Mars, которую вы использовали, был 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"
},
В этой задаче вы начнете работать с типом свойства Mars, чтобы добавить изображение знака доллара к свойствам на обзорной странице, которые выставлены на продажу.
Шаг 1. Обновите MarsProperty, указав тип
Класс MarsProperty
определяет структуру данных для каждого свойства, предоставляемого веб-службой. В предыдущей кодовой лаборатории вы использовали библиотеку Moshi для анализа необработанного ответа JSON от веб-службы Mars на отдельные объекты данных MarsProperty
.
На этом шаге вы добавляете некоторую логику в класс MarsProperty
, чтобы указать, сдается ли свойство в аренду или нет (то есть, является ли тип строки "rent"
или "buy"
). Вы будете использовать эту логику более чем в одном месте, поэтому лучше иметь ее здесь, в классе данных, чем копировать ее.
- Откройте приложение MarsRealEstate из последней лаборатории кода. (Вы можете загрузить MarsRealEstateGrid , если у вас нет приложения.)
- Откройте
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 для элементов сетки.
- Откройте
res/layout/grid_view_item.xml
. Это файл макета для каждой отдельной ячейки в макете сетки дляRecyclerView
. В настоящее время файл содержит только элемент<ImageView>
для изображения свойства. - Внутри элемента
<data>
добавьте элемент<import>
для классаView
. Вы используете импорт, когда хотите использовать компоненты класса внутри выражения привязки данных в файле макета. В этом случае вы собираетесь использоватьView.GONE
иView.VISIBLE
, поэтому вам нужен доступ к классуView
.
<import type="android.view.View"/>
- Окружите весь вид изображения с помощью
FrameLayout
, чтобы разрешить рисование знака доллара поверх изображения свойства.
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp">
<ImageView
android:id="@+id/mars_image"
...
</FrameLayout>
- Для
ImageView
измените атрибутandroid:layout_height
наmatch_parent
, чтобы заполнить новый родительскийFrameLayout
.
android:layout_height="match_parent"
- Добавьте второй элемент
<ImageView>
чуть ниже первого внутриFrameLayout
. Используйте определение, показанное ниже. Это изображение появляется в правом нижнем углу элемента сетки поверх изображения Марса и использует объект рисования, определенный в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"/>
- Добавьте атрибут
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>
- Скомпилируйте и запустите приложение. Обратите внимание, что свойства, которые не сдаются в аренду, отмечены значком доллара.
В настоящее время ваше приложение отображает все свойства Марса в обзорной сетке. Если бы пользователь покупал недвижимость на Марсе для сдачи в аренду, было бы полезно иметь значки, указывающие, какие из доступных свойств выставлены на продажу, но на странице все еще есть много свойств, которые нужно прокручивать. В этой задаче вы добавляете меню опций к фрагменту обзора, которое позволяет пользователю отображать только арендуемые объекты, только недвижимость для продажи или показывать все.
Один из способов выполнить эту задачу — проверить тип каждого MarsProperty
в обзорной сетке и отобразить только соответствующие свойства. Однако фактический веб-сервис Mars имеет параметр запроса или опцию (называемую filter
), которая позволяет вам получить только недвижимость типа rent
или buy
. Вы можете использовать этот запрос фильтра с URL-адресом веб-службы realestate
в браузере следующим образом:
https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buy
В этой задаче вы измените класс MarsApiService
, чтобы добавить параметр запроса к запросу веб-службы с помощью Retrofit. Затем вы подключаете меню параметров, чтобы повторно загрузить все данные о свойствах Марса, используя этот параметр запроса. Поскольку ответ, который вы получаете от веб-службы, содержит только интересующие вас свойства, вам вообще не нужно изменять логику отображения представления для сетки обзора.
Шаг 1. Обновите службу Mars API
Чтобы изменить запрос, вам нужно снова обратиться к классу MarsApiService
, который вы реализовали в первой лаборатории кода в этой серии. Вы изменяете класс, чтобы предоставить фильтрующий API.
- Откройте
network/MarsApiService.kt
. Сразу после импорта создайтеenum
MarsApiFilter
, чтобы определить константы, соответствующие значениям запроса, ожидаемым веб-службой.
enum class MarsApiFilter(val value: String) {
SHOW_RENT("rent"),
SHOW_BUY("buy"),
SHOW_ALL("all") }
- Измените метод
getProperties()
, чтобы он принимал строковые входные данные для запроса фильтра, и аннотируйте эти входные данные с помощью@Query("filter")
, как показано ниже.
Импортируйтеretrofit2.http.Query
при появлении запроса.
Аннотация@Query
указывает методуgetProperties()
(и, следовательно, Retrofit) сделать запрос веб-службы с параметром фильтра. Каждый раз, когда вызываетсяgetProperties()
, URL-адрес запроса включает часть?filter=type
, которая предписывает веб-службе отвечать с результатами, соответствующими этому запросу.
fun getProperties(@Query("filter") type: String):
Шаг 2. Обновите модель обзора обзора
Вы запрашиваете данные из MarsApiService
в getMarsRealEstateProperties()
в OverviewViewModel
. Теперь вам нужно обновить этот запрос, чтобы принять аргумент фильтра.
- Откройте
overview/OverviewViewModel.kt
. Вы увидите ошибки в Android Studio из-за изменений, внесенных на предыдущем шаге. ДобавьтеMarsApiFilter
(перечисление возможных значений фильтра) в качестве параметраgetMarsRealEstateProperties()
.
Импортируйтеcom.example.android.marsrealestate.network.MarsApiFilter
по запросу.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
- Измените вызов
getProperties()
в службе Retrofit, чтобы передать этот запрос фильтра в виде строки.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
- В блоке
init {}
передайтеMarsApiFilter.SHOW_ALL
в качестве аргумента дляgetMarsRealEstateProperties()
, чтобы отобразить все свойства при первой загрузке приложения.
init {
getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
- В конце класса добавьте метод
updateFilter()
, который принимает аргументMarsApiFilter
и вызываетgetMarsRealEstateProperties()
с этим аргументом.
fun updateFilter(filter: MarsApiFilter) {
getMarsRealEstateProperties(filter)
}
Шаг 3: Подключите фрагмент к меню опций
Последний шаг — подключить дополнительное меню к фрагменту для вызова updateFilter()
в модели представления, когда пользователь выбирает пункт меню.
- Откройте
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>
- Откройте
overview/OverviewFragment.kt
. В конце класса реализуйте методonOptionsItemSelected()
для обработки выбора пунктов меню.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
}
- В
onOptionsItemSelected()
вызовите методupdateFilter()
для модели представления с соответствующим фильтром. Используйте блок Kotlinwhen {}
для переключения между параметрами. Используйте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
}
- Скомпилируйте и запустите приложение. Приложение запускает первую сетку обзора со всеми типами недвижимости и свойствами для продажи, отмеченными значком доллара.
- Выберите « Аренда » в меню параметров. Свойства перезагружаются, и ни одно из них не отображается со значком доллара. (Отображается только сдаваемая в аренду недвижимость.) Возможно, вам придется подождать несколько секунд, пока экран обновится и отобразятся только отфильтрованные объекты.
- Выберите « Купить » в меню параметров. Свойства перезагружаются снова, и все они отображаются со значком доллара. (Показаны только выставленные на продажу объекты.)
Теперь у вас есть прокручивающаяся сетка значков свойств Марса, но пришло время получить более подробную информацию. В этой задаче вы добавляете фрагмент сведений для отображения сведений об определенном свойстве. Детальный фрагмент покажет увеличенное изображение, цену и тип недвижимости — аренда или продажа.
Этот фрагмент запускается, когда пользователь касается изображения в обзорной сетке. Для этого вам нужно добавить прослушиватель onClick
к элементам сетки RecyclerView
, а затем перейти к новому фрагменту. Вы перемещаетесь, инициируя изменение LiveData
в ViewModel
, как вы это делали на протяжении всех этих уроков. Вы также используете подключаемый модуль Safe Args компонента Navigation для передачи выбранной информации MarsProperty
из фрагмента обзора во фрагмент подробностей.
Шаг 1. Создайте модель подробного представления и обновите макет узла
Аналогично процессу, который вы использовали для обзорной модели представления и фрагментов, теперь вам необходимо реализовать модель представления и файлы компоновки для фрагмента сведений.
- Откройте
detail/DetailViewModel.kt
. Подобно тому, как связанные с сетью файлы Kotlin содержатся вnetwork
папке и файлы обзора вoverview
,detail
сведений содержит файлы, связанные с представлением сведений. Обратите внимание, что классDetailViewModel
(сейчас пустой) принимаетmarsProperty
в качестве параметра в конструкторе.
class DetailViewModel( marsProperty: MarsProperty,
app: Application) : AndroidViewModel(app) {
}
- Внутри определения класса добавьте
LiveData
для выбранного свойства Mars, чтобы представить эту информацию в подробном представлении. Следуйте обычному шаблону созданияMutableLiveData
для хранения самогоMarsProperty
, а затем предоставьте неизменяемое общедоступное свойствоLiveData
.
Импортируйтеandroidx.lifecycle.LiveData
и импортируйтеandroidx.lifecycle.MutableLiveData
по запросу.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
get() = _selectedProperty
- Создайте блок
init {}
и задайте значение выбранного свойства Mars с помощью объектаMarsProperty
из конструктора.
init {
_selectedProperty.value = marsProperty
}
- Откройте
res/layout/fragment_detail.xml
и просмотрите его в режиме конструктора.
Это файл макета для фрагмента детали. Он содержитImageView
для большой фотографии,TextView
для типа недвижимости (аренда или продажа) иTextView
для цены. Обратите внимание, что макет ограничения заключен вScrollView
, поэтому он будет автоматически прокручиваться, если представление становится слишком большим для отображения, например, когда пользователь просматривает его в ландшафтном режиме. - Перейдите на вкладку « Текст » для макета. В верхней части макета, непосредственно перед элементом
<ScrollView>
, добавьте элемент<data>
, чтобы связать модель подробного представления с макетом.
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
- Добавьте атрибут
app:imageUrl
в элементImageView
. Установите его вimgSrcUrl
из выбранного свойства модели представления.
Адаптер привязки, который загружает изображение с помощью Glide, также будет автоматически использоваться здесь, поскольку этот адаптер отслеживает все атрибутыapp:imageUrl
.
app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"
Шаг 2. Определите навигацию в обзорной модели представления
Когда пользователь нажимает на фотографию в обзорной модели, это должно инициировать переход к фрагменту, который показывает подробности о выбранном элементе.
- Откройте
overview/OverviewViewModel.kt
. Добавьте_navigateToSelectedProperty
MutableLiveData
и предоставьте ему неизменяемыйLiveData
.
Когда этиLiveData
ненулевыми, запускается навигация. (Вскоре вы добавите код для наблюдения за этой переменной и запуска навигации.)
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
get() = _navigateToSelectedProperty
- В конце класса добавьте метод
displayPropertyDetails()
, который устанавливает _navigateToSelectedProperty
для выбранного свойства Mars.
fun displayPropertyDetails(marsProperty: MarsProperty) {
_navigateToSelectedProperty.value = marsProperty
}
- Добавьте метод
displayPropertyDetailsComplete()
, который обнуляет значение_navigateToSelectedProperty
. Это нужно, чтобы отметить состояние навигации как завершенное и избежать повторного запуска навигации, когда пользователь возвращается из подробного представления.
fun displayPropertyDetailsComplete() {
_navigateToSelectedProperty.value = null
}
Шаг 3. Настройте прослушиватели кликов в сетевом адаптере и фрагменте.
- Откройте
overview/PhotoGridAdapter.kt
. В конце класса создайте собственный классOnClickListener
, который принимает лямбду с параметромmarsProperty
. Внутри класса определитеonClick()
, для которой задан параметр lambda.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
- Прокрутите вверх до определения класса
PhotoGridAdapter
и добавьте в конструктор частное свойствоOnClickListener
.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
- Сделайте фото кликабельным, добавив
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)
}
- Откройте
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
, передаваемого фрагменту сведений. Для этого вы используете Safe Args из компонента навигации.
- Откройте
res/navigation/nav_graph.xml
. Щелкните вкладку « Текст », чтобы просмотреть код XML для навигационного графика. - Внутри элемента
<fragment>
для фрагмента сведений добавьте элемент<argument>
, показанный ниже. Этот аргумент, названныйselectedProperty
, имеет типMarsProperty
.
<argument
android:name="selectedProperty"
app:argType="com.example.android.marsrealestate.network.MarsProperty"
/>
- Скомпилируйте приложение. Навигация выдает ошибку, потому что
MarsProperty
не подлежит разделу . ИнтерфейсParcelable
позволяет сериализовать объекты, чтобы данные объектов можно было передавать между фрагментами или действиями. В этом случае, чтобы данные внутри объектаMarsProperty
передавались во фрагмент детали через Safe Args,MarsProperty
должен реализовать интерфейсParcelable
. Хорошая новость заключается в том, что Kotlin предоставляет простой способ реализации этого интерфейса. - Откройте
network/MarsProperty.kt
. Добавьте аннотацию@Parcelize
в определение класса.
Импортируйтеkotlinx.android.parcel.Parcelize
по запросу.
Аннотация@Parcelize
использует расширения Kotlin Android для автоматической реализации методов в интерфейсеParcelable
для этого класса. Вам больше ничего не нужно делать!
@Parcelize
data class MarsProperty (
- Измените определение класса
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: Соедините фрагменты
Вы по-прежнему не осуществляете навигацию — фактическая навигация происходит во фрагментах. На этом шаге вы добавляете последние биты для реализации навигации между фрагментами обзора и подробностей.
- Откройте
overview/OverviewFragment.kt
. ВonCreateView()
под строками, которые инициализируют адаптер сетки фотографий, добавьте строки, показанные ниже, для наблюдения заnavigatedToSelectedProperty
из модели обзора обзора.
Импортируйтеandroidx.lifecycle.Observer
иandroidx.navigation.fragment.findNavController
по запросу.
Наблюдатель проверяет, не равен лиMarsProperty
—it
в лямбде — null, и если да, то получает навигационный контроллер из фрагмента с помощьюfindNavController()
. ВызовитеdisplayPropertyDetailsComplete()
, чтобы указать модели представления сброситьLiveData
в нулевое состояние, чтобы вы случайно не активировали навигацию снова, когда приложение вернется обратно кOverviewFragment
.
viewModel.navigateToSelectedProperty.observe(this, Observer {
if ( null != it ) {
this.findNavController().navigate(
OverviewFragmentDirections.actionShowDetail(it))
viewModel.displayPropertyDetailsComplete()
}
})
- Открыть
detail/DetailFragment.kt
. Добавьте эту строку сразу после вызоваsetLifecycleOwner()
вonCreateView()
. Эта строка получает выбранный объектMarsProperty
из Safe Args.
Обратите внимание на использование ненулевого оператора утверждения Kotlin (!!
). ЕслиselectedProperty
там нет, значит, произошло что-то ужасное, и вы действительно хотите, чтобы код выдавал нулевой указатель. (В производственном коде вы должны каким-то образом обрабатывать эту ошибку.)
val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
- Добавьте эту строку, чтобы получить новый
DetailViewModelFactory
. Вы будете использоватьDetailViewModelFactory
для получения экземпляраDetailViewModel
. Стартовое приложение включает в себя реализациюDetailViewModelFactory
, поэтому все, что вам нужно сделать здесь, это инициализировать ее.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
- Наконец, добавьте эту строку, чтобы получить модель
DetailViewModel
с завода и соединить все части.
binding.viewModel = ViewModelProviders.of(
this, viewModelFactory).get(DetailViewModel::class.java)
- Скомпилируйте и запустите приложение, а затем коснитесь любой фотографии Марса. Фрагмент сведений появляется для сведений об этом свойстве. Нажмите кнопку «Назад», чтобы вернуться на страницу обзора, и обратите внимание, что экран подробностей по-прежнему немного скуден. Вы закончите добавлять данные свойства на эту страницу сведений в следующей задаче.
Прямо сейчас на странице сведений отображается только та же фотография Марса, которую вы привыкли видеть на странице обзора. Класс MarsProperty
также имеет тип недвижимости (аренда или покупка) и цену недвижимости. Детальный экран должен включать оба этих значения, и было бы полезно, если бы в арендной собственности указывалось, что цена указана за месяц. Вы используете преобразования LiveData
в модели представления для реализации обеих этих вещей.
- Откройте
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>
- Откройте
detail/DetailViewModel.kt
. В нижней части класса добавьте код, показанный ниже.
Импортируйтеandroidx.lifecycle.Transformations
, если требуется.
Это преобразование проверяет, является ли выбранное имущество сдаваемым в аренду, используя тот же тест из первой задачи. Если свойство является арендным, преобразование выбирает соответствующую строку из ресурсов с помощью Kotlinwhen {}
. Обе эти строки нуждаются в числе в конце, поэтому впоследствии вы объединяете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)
}
- Импортируйте сгенерированный класс
R
, чтобы получить доступ к строковым ресурсам в проекте.
import com.example.android.marsrealestate.R
- После преобразования
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
}))
}
- Откройте
res/layout/fragment_detail.xml
. Осталось сделать еще одну вещь — привязать новые строки (созданные с помощью преобразованийLiveData
) к подробному представлению. Для этого вы устанавливаете значение текстового поля для текста типа свойства вviewModel.displayPropertyType
, а текстовое поле для текста значения цены — в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" />
- Скомпилируйте и запустите приложение. Теперь все данные о свойствах отображаются на странице сведений в красивом формате.
Проект Android Studio: MarsRealEstateFinal
Выражения привязки
- Используйте выражения привязки в файлах макета XML для выполнения простых программных операций, таких как математические или условные тесты, над связанными данными.
- Чтобы ссылаться на классы внутри файла макета, используйте
<import>
внутри тега<data>
.
Параметры запроса веб-службы
- Запросы к веб-сервисам могут включать необязательные параметры.
- Чтобы указать параметры запроса в запросе, используйте аннотацию
@Query
в Retrofit .
Удасити курс:
Документация для разработчиков Android:
- Обзор ViewModel
- Обзор LiveData
- Адаптеры для привязки
- Макеты и выражения привязки
- Навигация
- Начните работу с компонентом навигации
- Передача данных между пунктами назначения (также описывает безопасные аргументы)
-
Transformations
класса - Класс
ViewModelProvider
- Класс
ViewModelProvider.Factory
Другой:
В этом разделе перечислены возможные домашние задания для студентов, которые работают с этой кодовой лабораторией в рамках курса, проводимого инструктором. Инструктор должен сделать следующее:
- При необходимости задайте домашнее задание.
- Объясните учащимся, как сдавать домашние задания.
- Оценивайте домашние задания.
Преподаватели могут использовать эти предложения так мало или так часто, как они хотят, и должны свободно давать любые другие домашние задания, которые они считают подходящими.
Если вы работаете с этой кодовой лабораторией самостоятельно, не стесняйтесь использовать эти домашние задания, чтобы проверить свои знания.
Ответьте на эти вопросы
Вопрос 1
Что делает <import>
в XML-файле макета?
▢ Включить один файл макета в другой.
▢ Вставьте код Kotlin в файл макета.
▢ Обеспечьте доступ к свойствам с привязкой к данным.
▢ Позволяет ссылаться на классы и члены классов в выражениях привязки.
вопрос 2
Как добавить параметр запроса к вызову веб-службы REST в Retrofit?
▢ Добавьте запрос в конец URL-адреса запроса.
▢ Добавьте параметр для запроса в функцию, которая делает запрос, и аннотируйте этот параметр с помощью @Query
.
▢ Используйте класс Query
для создания запроса.
▢ Используйте метод addQuery()
в построителе дооснащения.
Начать следующий урок:
Ссылки на другие лаборатории кода в этом курсе см. на целевой странице лаборатории кода Android Kotlin Fundamentals .