# RecyclerView
## Оглавление
- [Введение](#введение)
- [Шаблон проектирования "Адаптер"](#шаблон-проектирования-адаптер)
- [Добавление `RecyclerView`](#добавление-recyclerview)
- [Отображение записей `SleepNight` в списке `RecyclerView`](#отображение-записей-sleepnight-в-списке-recyclerview)
- [Рефакторинг класса `SleepNightAdapter`](#рефакторинг-класса-sleepnightadapter)
## Введение
Отображение списка данных является одной из самых распространенных задач пользовательского интерфейса в Android. В погодном приложении отображается список прогнозов. В приложении чата отображается список сообщений. В новостном приложении — список новостей. Почти каждое приложение имеет хотя бы один список.
Списки как могут быть простыми, где каждый элемент — это просто текст, отображаемый компонентом `TextView`. А могут быть и сложными с элементами, состоящими из множества компонентов.
Для реализации разных вариантов списков, как простых так и сложных, в Android используется компонент `RecyclerView`. У `RecyclerView` есть несколько преимуществ.
Во-первых, `RecyclerView` разработан так, что он работает со списками максимально эффективно, даже когда в списке находится большое число элементов.
Во-вторых, `RecyclerView` позволяет отображать элементы разной сложности одновременно. Это могут быть как простые `TextView`-элементы, так и сложные элементы с текстом, изображениями или видео-вставками внутри.
В-третьих, `RecyclerView` поддерживает возможность отображения и списков, и сетки (Grid) из элементов.
В-четвертых, `RecyclerView` позволяет настраивать ориентацию скроллинга: вертикально или горизонтально.
Эффективность `RecyclerView` заключается в том, что он использует адаптеры (о них речь пойдет далее) для отрисовки элементов списка. По умолчанию `RecyclerView` отрисовывает только элементы, видимые пользователю на экране. Это означает, что если в списке 1000 элементов, а пользователю видны только 10 из них, то `RecyclerView` будет отрисовывать только 10 элементов из 1000. А во время скроллинга будет автоматически обновлять содержимое новых элементов, которые пользователю становятся видны. Таким образом `RecyclerView` просто переиспользует существующие элементы, чтобы обновить отображение, вместо полного их пересоздания. Кроме этого, когда обновляется какой-либо элемент списка (например, в результате работ фоновых процессов), то `RecyclerView` не будет обновлять весь список целиком, а обновит только один конкретный элемент.
Кроме `RecyclerView` Android предоставляет и другие компоненты для отображения списков.
Например, `ListView` и `GridView`. Они более просты в использовании, чем `RecyclerView`, однако они менее настраиваемые, и эффективно работают лишь с небольшим количеством элементов в списке, не более 100.
Также есть `LinearLayout`, однако, он не подходит для отображения большого числа элементов, генерация которых должна выполняться динамически. `LinearLayout` подходит лишь для отображения статических компонентов, количество которых меняться не должно.
Таким образом, на текущий момент наиболее подходящим решением для отображения списка элементов является `RecyclerView`.
В данном уроке мы будем добавлять `RecyclerView` в приложение "Sleep Tracker", которое разрабатывалось ранее. Сейчас приложение отображает список данных, но отображает их с помощью одного тестового поля `TextView`, которое заполняется постоянно генерирующейся строкой со всеми данными БД. Это ужасно выглядит с точки зрения пользовательского интерфейса, ужасно реализовано с точки зрения производительности (строка постоянно генерируется заново), и ужасно выглядит с точки зрения чистоты кода.
Для отображения списка элементов будет использоваться `RecyclerView` и будут реализованы элементы списка, отображающие время сна, его продолжительность, оценку и иконку оценки.
![](sleep-tracker-with-recyclerview.png)
## Шаблон проектирования "Адаптер"
В этом уроке будет рассмотрен шаблон проектирования — "Адаптер". Он потребуется для правильной реализации работы с `RecyclerView`.
`RecyclerView` служит для отображения большого количества однотипных данных, например записей какой-либо одной таблицы базы данных. Прежде чем отобразить данные, кто-то должен их подготовить для отображения, создать отдельные элементы списка и поместить на них данные. Этим занимается адаптер.
В случае с приложением "Sleep Tracker" данные хранятся в базе данных `Room`. Адаптер необходим для адаптации и подготовки данных к отображению в `RecyclerView`.
Адаптер должен предоставлять для `RecyclerView`:
* Информацию о количестве доступных элементов.
* Информацию о том, каким образом элементы списка должны быть отрисованы.
* Информацию о том, каким образом должен быть создан новый UI для элементов списка.
Когда `RecyclerView` запущен, он опрашивает адаптер о том, как много элементов он должен содержать. Затем узнает каким образом первый элемент должны быть отрисован на экране и отрисовывает его. Затем узнает как отрисовать второй элемент и отрисовывает его тоже, и т.д до тех пор пока место на экране не закончится. В этот момент `RecyclerView` заканчивает отрисовку элементов.
Когда пользователь начинает скроллить экран, элемент, скрывающийся от пользователя, переиспользуется для создания нового элемента, который пользователю показывается во время скроллинга.
Для реализации переиспользования экземпляров и поддержки сложных видов для элементов списка `RecyclerView` используются "холдеры видов" `ViewHolder`.
`ViewHolder`:
* Содержит в себе информацию о виде элемента списка.
* Хранит важную информацию для эффективной работы `RecyclerView`, например, последнюю позицию текущего элемента в списке.
* Является интерфейсом для элементов `RecyclerView`.
Можно подвести небольшое резюме касательно шаблона "Адаптер".
Наше приложение имеет некоторый список элементов (записей о снах). Адаптер будет подготавливать элементы списка для отрисовки их на экране. Отображаться элементы будут в рамках UI-компонента `RecyclerView`. `RecyclerView` может запрашивать количество элементов, которые нужно отобразить, у адаптера. Также он будет запрашивать у адаптера новый экземпляр `ViewHolder` для каждого элемента, который должен быть отрисован. Для эффективности `RecyclerView` будет переиспользовать `ViewHolder`'ы, которые скрываются с экрана в процессе скроллинга.
![](adapter-pattern.png)
Далее перейдем к добавлению `RecyclerView`-, `Adapter-` и `ViewHolder`-классов шаг за шагом.
## Добавление `RecyclerView`
Перейдем к добавлению `RecyclerView` на макет фрагмента `SleepTrackerFragment` и реализации класса-адаптера.
**1. Добавление `RecyclerView` в файл `fragment_sleep_tracker.xml`:**
На текущий момент макет `fragment_sleep_tracker.xml` содержит компонент `ScrollView` с `TextView` внутри. Наша задача — заменить `ScrollView` на `RecyclerView`. Именно он будет отображать список элементов.
```kotlin
```
Компонент `RecyclerView` имеет идентификатор `sleep_list`. Он потребуется позже. Кроме этого компонент располагается внутри `ConstraintLayout`, поэтому для него нет необходимости строго настраивать ширину и высоту, но обязательно настраиваются "привязки" к краям экрана слева и справа, а также к кнопке "Stop" сверху и кнопке "Clear" снизу. Список записей располагается между этими кнопками. В конце настраивается параметр `layoutManager`. На вход параметр принимает имя класса `LinearLayoutManager`, который определяет расположение элементов внутри `RecyclerView` и политику взаимодействия с ними. Кроме `LinearLayoutManager` это могут быть также `GridLayoutManager`, `StaggeredGridLayoutManager` и `WearableLinearLayoutManager`. `LinearLayoutManager` определяет, что элементы внутри `RecyclerView` будут располагаться в виде списка.
**2. Добавление класса `SleepNightAdapter`:**
Для добавления адаптера необходимо создать новый класс `SleepNightAdapter`. Он должен наследоваться от шаблонного класса `RecyclerView.Adapter<>` и в качестве шаблона указывается `ViewHolder`-класс `TextItemViewHolder`, который будет создан следующим.
```kotlin
class SleepNightAdapter : RecyclerView.Adapter() {
}
```
**3. Добавление класса `TextItemViewHolder`:**
Для тестов нам необходимо просто отобразить текст в элементах списка, поэтому создадим простейший `TextItemViewHolder` с элементом `TextView` внутри.
```kotlin
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
```
Добавленный код компилятор не соберет, поскольку необходимо переопределить несколько методов класса-адаптера, чтобы предоставить `RecyclerView` информацию о данных и видах элементов.
**4. Добавление данных в адаптер:**
Для начала добавим свойство `data`, которое будет являться списком объектов `SleepNight`. Это будут данные для `RecyclerView`.
```kotlin
var data = listOf()
set(value) {
field = value
notifyDataSetChanged()
}
```
Для свойства `data` определим сеттер, который будет не только сохранять новое значение свойства, но и вызывать метод адаптера `notifyDataSetChanged()`. Данный метод оповещает `RecyclerView` о том, что данные изменились и `RecyclerView`, получая такое сообщение, автоматически перерисовывает свое содержимое.
Далее необходимо предоставить информацию о том, сколько элементов будет содержаться в `RecyclerView`. Для этого необходимо переопределить метод `getItemCount()`, чтобы он возвращал общее число элементов в списке `data`.
```kotlin
override fun getItemCount() = data.size
```
**5. Предоставление информации об отрисовке элементов списка:**
Далее необходимо предоставить информацию о том, каким образом элементы должны быть отрисованы. Для этого переопределяется метод `onBindViewHolder()`. Он принимает на вход два параметра: `ViewHolder` (в нашем случае `TextItemViewHolder`) и позицию элемента. Метод `onBindViewHolder()` будет вызван `RecyclerView` для отображения данных на элементе с определенной позицией в списке.
```kotlin
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
val item = data[position]
holder.textView.text = item.sleepQuality.toString()
}
```
Для обновления данных на элементе `TextItemViewHolder` необходимо сперва получить экземпляр модели `SleepNight` из списка `data` по позиции. А затем получить, например, значение `sleepQuality` и записать его в `TextView` "холдера", который отвечает за отображение элемента в списке. Значение `sleepQuality` используется исключительно для демонстрации того, как получить данные и поместить их в вид элемента списка.
Таким образом мы создали адаптер и настроили для `RecyclerView` возможность получения информации о количестве элементов, и о том, как данные должны помещаться на вид каждого из элементов.
**6. Добавление метода создания экземпляров `TextItemViewHolder`:**
Далее перейдем к переопределению метода для создания экземпляров `ViewHolder` — `onCreateViewHolder()`.
```kotlin
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.text_item_view, parent, false) as TextView
return TextItemViewHolder(view)
}
```
Метод принимает на вход два параметра. Первый — родительский UI-элемент типа `ViewGroup`, куда будет помещен создаваемый `ViewHolder`. В нормальной ситуации это именно компонент `RecyclerView`. Второй параметр — тип, используемый, когда `RecyclerView` отображает визуальные элементы разных типов и видов. Например, когда `RecyclerView` может отображать в одном списке элементы с текстом, и элементы с изображениями одновременно.
Точно так же как и для инициализации вида фрагментов, здесь для инициализации вида `ViewHolder` используется `LayoutInflater` и метод `inflate()`. Метод `inflate()` загружает макет вида элемента списка `text_item_view`, инициализирует элемент `TextView` и возвращает его. В конце возвращаем новый экземпляр "холдера" `TextItemViewHolder`. Конструктор `TextItemViewHolder` принимает на вход экземпляр `TextView`.
Загружаемый макет `text_item_view.xml` "холдера" содержит лишь один компонент `TextView`.
```xml
```
Таким образом мы подготовились к созданию экземпляров `ViewHolder`-компонентов, которые `RecyclerView` будет использовать для отображения в списке. Сам `RecyclerView` ничего не знает том, как выглядят элементы внутри него. Он лишь будет запрашивать адаптер на создание элементов, а адаптер уже предоставит всю информацию об элементах: их количестве, создании "холдеров" и об отрисовке данных на "холдерах".
**7. Установка адаптера для `RecyclerView`:**
Осталось создать экземпляр адаптера и установить его для добавленного ранее `RecyclerView`. Для этого необходимо в классе `SleepTrackerFragment` в методе `onCreateView()` добавить инициализацию адаптера и установку с помощью поля `binding`.
```kotlin
// SleepTrackerFragment.onCreateView()
val adapter = SleepNightAdapter()
binding.sleepList.adapter = adapter
```
Таким образом адаптер установлен в `RecyclerView` и все данные из адаптера автоматически будут отображаться в `RecyclerView` с помощью `ViewHolder`-компонентов.
Чтобы адаптер содержал некоторые данные необходимо проинициализировать его поле `data`, когда данные в БД меняются. Поскольку доступ к данным в БД осуществляется через объект `ViewModel`, то необходимо добавить подписку на изменение этих данных.
```kotlin
viewModel.nights.observe(viewLifecycleOwner, Observer { nights ->
if (nights != null)
adapter.data = nights
})
```
Если запустить приложение, то можно убедиться, что при нажатии кнопки "Start" в список на экране добавляется один элемент, отображающий "-1" — это оценка сна по умолчанию. После нажатия кнопки "Stop" открывается экран выбора оценки и после выбора запись на странице со списком обновляется и отображает ту оценку, что выбрал пользователь. Этот тест показывает, что добавленный `RecyclerView` работает. Он получает данные из адаптера (который в свою очередь получает их из БД через `ViewModel`). Адаптер и `ViewHolder` также предоставляют для `RecyclerView` информацию о том, как должны выглядеть элементы списка и как их создать.
**8. Повторное использование экземпляров `ViewHolder`:**
Чтобы продемонстрировать каким образом элементы списка `RecyclerView` переипользуются, необходимо в переопределенный метод `onBindViewHolder()` добавить условие о том, что, если оценка качества сна менее двойки, то необходимо окрашивать текст с оценкой красным цветом.
```kotlin
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
val item = data[position]
if (item.sleepQuality < 2) {
holder.textView.setTextColor(Color.RED)
}
holder.textView.text = item.sleepQuality.toString()
}
```
Если запустить приложение и добавить столько элементов в список, что они не будут помещаться на экране все одновременно, то можно заметить при добавлении новых элементов, что уже ранее добавленные элементы с оценкой 2 и выше окрашиваются красным, хотя не должны. Это происходит из-за переиспользования экземпляров `ViewHolder`-объектов, у которых уже установлен цвет, но переопределяется значение текста. Чтобы избежать подобных проблем, нужно сбрасывать состояние элементов в состояние по умолчанию.
В данном случае, необходимо для элементов с оценкой 3 и выше устанавливать черный цвет текста.
```kotlin
if (item.sleepQuality < 2) {
holder.textView.setTextColor(Color.RED)
} else {
holder.textView.setTextColor(Color.BLACK)
}
```
Теперь при запуске приложения и добавлении новых элементов можно заметить, что проблемы с неверным окрашиванием элементов списка нет.
## Отображение записей `SleepNight` в списке `RecyclerView`
В предыдущей главе научились добавлять `RecyclerView` с адаптером и `ViewHolder`'ом для отображения конкретных данных на отдельных элементах списка. В этой главе подробнее пойдет речь о `ViewHolder`'ах, о создании собственного сложного `ViewHolder`-класса.
Прежде чем создавать собственный сложный `ViewHolder`-класс рассмотрим класс `TextItemViewHolder`, который используется в данный момент.
```kotlin
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
```
Класс наследуется от `RecyclerView.ViewHolder`, имеет лишь одной свойство `textView` и не имеет тела. То есть вся реализация данного класса заключается в реализации его родителя `RecyclerView.ViewHolder`.
Если заглянуть в реализацию или [документацию](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ViewHolder) класса `RecyclerView.ViewHolder`, можно найти свойства и методы, общие для `ViewHolder`-классов.
Например, каждый `ViewHoler` содержит свойство `itemView`. Объект `itemView` является видом элемента списка и экземпляром класса `View`, который должен быть отображен внутри `RecyclerView`. В случае с `TextItemViewHolder` таким видом является `TextView`, который передается в конструктор класса.
Перейдем к созданию собственного `ViewHolder`-класса.
**1. Добавление макета для `ViewHolder`:**
Сперва необходимо добавить макет элемента списка, который будет макетом для нового `ViewHolder`-класса. Файл с макетом будет называться `list_item_sleep_night.xml`.
Макет должен содержать один элемент `ImageView` для отображения иконки оценки сна и два текстовых поля `TextView` для отображения даты сна и текстового представления оценки.
![](list_item_sleep_night.png)
Компонент `ImageView` для отображения иконки:
```xml
```
Компонент `TextView` для отображения даты сна:
```xml
```
Компонент `TextView` для отображения текстового представления оценки:
```xml
```
Подробно на каждом атрибуте останавливаться не будем. Все было рассмотрено в предыдущих уроках.
**2. Создание класса `SleepNightViewHolder`:**
Далее необходимо создать класс `SleepNightViewHolder` и унаследовать его от `RecyclerView.ViewHolder`.
```kotlin
class SleepNightViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)
val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
val quality: TextView = itemView.findViewById(R.id.quality_string)
}
```
Класс содержит свойства `qualityImage`, `sleepLength` и `quality`, которые инициализируются с помощью `findViewById()` и являются компонентами, которые располагаются на макете элемента списка `list_item_sleep_night.xml`.
**3. Использование `SleepNightViewHolder` в классе `SleepNightAdapter`:**
Далее необходимо заменить использование `TextItemViewHolder` в классе `SleepNightAdapter` на использование `SleepNightViewHolder`.
Во-первых, необходимо обновить метод `onCreateViewHolder()`: заменить тип возвращаемого значения на `SleepNightViewHolder`, ссылку на ресурс с макетом "холдера" на `R.layout.list_item_sleep_night` и создание экземпляра "холдера" на `SleepNightViewHolder(view)`.
```kotlin
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SleepNightViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night, parent, false)
return SleepNightViewHolder(view)
}
```
Во-вторых, требуется обновить реализацию метода `onBindViewHolder()`. Метод должен принимать на вход экземпляр `SleepNightViewHolder` и настраивать его вид в соответствии с данными.
```kotlin
override fun onBindViewHolder(holder: SleepNightViewHolder, position: Int) {
val item = data[position]
val res = holder.itemView.context.resources
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMillis, item.endTimeMillis, res)
holder.quality.text = convertNumericQualityToString(item.sleepQuality, res)
holder.qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}
```
Метод выполняет настройку текстовых полей `sleepLength` и `quality`, используя статические функции из файла `Util.kt`. Функция `convertNumericQualityToString()` уже добавлена заранее. А функцию `convertDurationToFormatted()` необходимо добавить вручную. Ее исходный код можно взять [здесь](https://github.com/udacity/andfun-kotlin-sleep-tracker-with-recyclerview/blob/Step.04-Solution-Display-SleepQuality-List/app/src/main/java/com/example/android/trackmysleepquality/Util.kt#L51):
```kotlin
private val ONE_MINUTE_MILLIS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES)
private val ONE_HOUR_MILLIS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS)
fun convertDurationToFormatted(startTimeMilli: Long, endTimeMilli: Long, res: Resources): String {
val durationMilli = endTimeMilli - startTimeMilli
val weekdayString = SimpleDateFormat("EEEE", Locale.getDefault()).format(startTimeMilli)
return when {
durationMilli < ONE_MINUTE_MILLIS -> {
val seconds = TimeUnit.SECONDS.convert(durationMilli, TimeUnit.MILLISECONDS)
res.getString(R.string.seconds_length, seconds, weekdayString)
}
durationMilli < ONE_HOUR_MILLIS -> {
val minutes = TimeUnit.MINUTES.convert(durationMilli, TimeUnit.MILLISECONDS)
res.getString(R.string.minutes_length, minutes, weekdayString)
}
else -> {
val hours = TimeUnit.HOURS.convert(durationMilli, TimeUnit.MILLISECONDS)
res.getString(R.string.hours_length, hours, weekdayString)
}
}
}
```
Кроме этого требуется добавить строки:
```xml
%d minutes on %s
%d hours on %s
%d seconds on %s
```
И ресурс с иконкой `ic_sleep_active` [отсюда](https://github.com/udacity/andfun-kotlin-sleep-tracker-with-recyclerview/blob/Step.04-Solution-Display-SleepQuality-List/app/src/main/res/drawable/ic_sleep_active.xml).
Кроме настройки текстовых полей, метод выполняет установку иконки оценки сна из ресурсов, в зависимости от значения поля `sleepQuality` записи данных.
Если запустить приложение, то можно удостовериться, что код работает. `RecyclerView` отображает список из элементов, где каждый элемент обладает видом, созданного класса `SleepNightViewHolder`. При добавлении новых записей иконки и текст на элементах обновляется.
## Рефакторинг класса `SleepNightAdapter`
Если посмотреть на класс `SleepNightAdapter`, то можно заметить, что он требует небольшого рефакторинга. Так, например, метод `onBindViewHolder` содержит код изменяющий внешний вид компонента `ViewHolder` и логичнее было бы перенести этот код в класс `SleepNightViewHolder`. Кроме того так мы будем соответствовать принципу инкапсуляции кода.
**1. Рефакторинг `onBindViewHolder():`**
Изменение свойств класса `SleepNightViewHolder` можно поместить в отдельный метод самого класса. Назвать его можно `bind()`. Кроме этого, переменную `res` можно сделать приватным свойством класса. Остальные свойства необходимо сделать приватными, чтобы следовать принципу инкапсуляции и не предоставлять прямой доступ к свойствам другим классам.
```kotlin
// SleepNightViewHolder
private val res: Resources = itemView.context.resources
private val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
private val quality: TextView = itemView.findViewById(R.id.quality_string)
private val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)
fun bind(item: SleepNight) {
sleepLength.text = convertDurationToFormatted(item.startTimeMillis, item.endTimeMillis, res)
quality.text = convertNumericQualityToString(item.sleepQuality, res)
qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}
```
В методе `onBindViewHolder()` останется только вызов этого метода с передачей объекта `SleepNight` как параметра.
```kotlin
// SleepNightAdapter
override fun onBindViewHolder(holder: SleepNightViewHolder, position: Int) {
val item = data[position]
holder.bind(item)
}
```
После выполнения рефакторинга необходимо запустить приложение, чтобы убедиться, что оно продолжает работать правильно.
**2. Рефакторинг `onCreateViewHolder():`**
Кроме выделения кода `onBindViewHolder()` в класс "холдера", туда же можно вынести и код метода `onCreateViewHolder()`. Таким образом будет инкапсулирована логика создания экземпляра `SleepNightViewHolder`.
Методу можно дать имя `from()` по аналогии с методом `LayoutInflater.from()`. Метод будет возвращать экземпляр `SleepNightViewHolder`, необходимый для `onCreateViewHolder()`, а принимать на вход будет экземпляр класса `ViewGroup` — родительского элемента.
Поскольку метод создает новый экземпляр класса `SleepNightViewHolder`, нет смысла вызывать его на экземпляре класса, логичнее было бы вызывать его на самом классе. Поэтому определение метода помещается в блок `companion object`.
```kotlin
companion object {
fun from(parent: ViewGroup): SleepNightViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night, parent, false)
return SleepNightViewHolder(view)
}
}
```
В методе `onCreateViewHolder()` остается только лишь вызов `SleepNightViewHolder.from()`.
```kotlin
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SleepNightViewHolder {
return SleepNightViewHolder.from(parent)
}
```
После выполнения рефакторинга также необходимо запустить приложение, чтобы убедиться, что оно продолжает работать правильно.