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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

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

image

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-разработчика и современные туториалы.
Источник: https://habr.com/ru/post/523840/

Интересные статьи

Интересные статьи

DDoS-атаке может подвергнуться любой ресурс. Чаще всего атаки носят целенаправленный характер — злоумышленники могут пытаться положить сайт с какой-то информацией (разного рода СМИ и...
Компания SpaceX, основанная небезызвестным Илон Маск, выпустила симулятор ручной стыковки корабля Crew Dragon с МКС. Если все пойдет по плану, стыковку проведут 27 мая 2020 года. Она ...
Некоторое время назад меня попросили сделать видео-инструкцию по настройке сети в операционной системе DOS. К сожалению в съёмке видео я не силен, поэтому постараюсь максимально подробно письменн...
В этой статье рассмотрим "Алгоритм X" Кнута и его применение для решения судоку. Прелесть алгоритма в том, что судоку при этом решается быстро без программирования каких-то продвинутых техник реш...
Здравствуйте. Я уже давно не пишу на php, но то и дело натыкаюсь на интернет-магазины на системе управления сайтами Битрикс. И я вспоминаю о своих исследованиях. Битрикс не любят примерно так,...