Как стать автором
Обновить

Делаем код в адаптере чище с помощью MergeAdapter

Время на прочтение3 мин
Количество просмотров5.7K
Надоели перегруженные и сложные адаптеры в вашем проекте, напоминающие картинку ниже? Каждый раз, при добавлении нового типа ячейки хочется переписать адаптер для RecyclerView, чтобы код читался проще? Есть множество подходов, чаще всего рекомендуется использовать подход delegate adapter или, например библиотеку для динамического создания списков с различными типами view как groupie о работе с которой вы можете ознакомиться в этой статье. Но сегодня расскажем о новом классе, который поможет инкапсулировать логику вашего адаптера для разных ячеек тем самым соответствовать принципам SOLID.

MergeAdapter — новый класс, появившейся в recyclerview:1.2.0-alpha02, который позволит объединить несколько адаптеров для отображения в едином RecyclerView. Это позволит инкапсулировать логику для каждой ячейки в своём адаптере, и позволит переиспользовать её в будущем.

Проблема


Начнем с примера. Предположим, у нас есть задача отобразить ленту с двумя типами данных — текст с описанием и картинка. Код в методе onCreateViewHolder в самом распространённом случае будет выглядеть так:

override fun onCreateViewHolder(
    parent: ViewGroup, viewType: Int
): RecyclerView.ViewHolder? {
    val holder: RecyclerView.ViewHolder
    val inflater = LayoutInflater.from(parent.context)
    when (viewType) {
        TEXT_VIEW_TYPE -> {
            holder = TextViewHolder(
                inflater.inflate(R.layout.text_item, parent, false)
            )
        }
        IMAGE_VIEW_TYPE -> {
            holder = ImageViewHolder(
                inflater.inflate(R.layout.image_item, parent, false),
                imageClickListener
            )
        }
        else -> {
            throw IllegalArgumentException(
                "Can't create view holder from view type $viewType"
            )
        }
    }
    return holder
}

Чем это плохо? Минус такой реализации в нарушении принципов DRY и SOLID (single responsibility и open closed). Чтобы в этом убедиться, достаточно добавить два требования: ввести новый тип данных (чекбокс) и еще одну ленту, где будут только чекбоксы и картинки.

Перед нами встает выбор — использовать этот же адаптер для второй ленты или создать новый? Независимо от решения, которое мы выберем, нам придется менять код (об одном и том же, но в разных местах). Надо будет добавить новый VIEW_TYPE, новый ViewHolder и отредактировать методы: getItemViewType(), onCreateViewHolder() и onBindViewHolder().

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

Если решим создать новый адаптер, то будет просто масса дублирующего кода.

Решение


Новый класс MergeAdapter, позволяет комбинировать разные адаптеры для разного вида ячеек. Например, очень частый кейс использования — отображать spinner на время загрузки данных в ленте, а если, вдруг произошла ошибка загрузки — отображать ячейку с ошибкой в конце ленты.

image

Решением данной задачи, может быть использование MergeAdapter Предположим, у нас есть 3 адаптера:


val firstAdapter: FirstAdapter = …
val secondAdapter: SecondAdapter = …
val thirdAdapter: ThirdAdapter = …val mergeAdapter = MergeAdapter(firstAdapter, secondAdapter, thirdAdapter)
recyclerView.adapter = mergeAdapter

RecyclerView отобразит элементы каждого адаптера последовательно, в том же порядке, в котором переданы в конструктор. Разные адаптеры позволяют разделить логику для разных ячеек в списке. Например, если необходимо добавить заголовок к списку, не нужно реализовывать эту логику в адаптере, который отвечает за отображение главного контента в списке, можно разделить адаптеры, для разных видов ячеек. Такой подход помогает инкапсулировать логику и переиспользовать её в будущем для разных экранов.

image

Отображение загрузки в заголовке или внизу списка.


Для отображения статуса загрузки вверху или внизу списка нужно добавить адаптеры соответственно:

val mergeAdapter = MergeAdapter(headerAdapter, listAdapter, footerAdapter)
recyclerView.adapter = mergeAdapter

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

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

Понравилась статья? Не забудьте присоединиться к нам в Telegram, а на платформе AndroidSchool.ru публикуются полезные материалы для Android-разработчика и современные туториалы.
Теги:
Хабы:
+2
Комментарии4

Публикации

Изменить настройки темы

Истории

Работа

iOS разработчик
23 вакансии
Swift разработчик
32 вакансии

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн