Concat Adapter для списка айтемов в BDUI

Привет, я Карим Насыбуллин, Android-разработчик в Яндекс Go. Около года занимаюсь развитием сервиса BUY&SELL. Недавно моей команде пришлось решить проблему использования довольно сложного BDUI-списка айтемов с пагинацией. В этой статье расскажу, как Concat Adapter помог нам с этой задачей.

Контекст

BUY&SELL — p2p-маркетплейс для торговли товарами между пользователями Яндекс Go. Продукт создавался в сжатые сроки и в условиях, близких к хакатону. Поэтому со временем потребовал развития — кроме статичных секций в него нужно было добавить более сложные. Например, кнопки с состояниями и пагинационный список. Первым шагом мы внедрили Backend Driven User Interface (далее BDUI) решение для некоторых экранов, однако логика сильно усложнилась и перед командой стояла задача найти способ упростить её.

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

01.png

Кроме статических элементов (например, картинок, сепараторов и текстов), внутри BDUI мы поддерживаем более сложные секции — кнопки с состояниями и табы.

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

items: [
{
  type: text,
},
{
  type: button,
},
	...
	sectionN
]

В секции хранится определённая информация для отрисовки страницы, а сама она может выглядеть так:

{
   type: "text",
   text: "some text",
   style: "style for text like font, size and etc",
}

Но есть одно исключение — секция табов. Среди секций может прийти, например, такой объект:

{
   type: tabs,
   type: tabs,
   tabs: [
        {
	    title: string,
	    items: [
		{
  type: offer,
},
		{
  type: offer,
},
		...
	    ]
	},
	{
	    title: TabTitle2,
	    items: [
		{
  type: text,
},
		{
  type: slot,
},
...
	    ]
	}
   ]
}
08.png

Отрисовка происходит в через BDUI-адаптер, который поддерживает несколько секций:

  • text — текст с настраиваемыми стилями и размерами с бэкенда, также поддерживающий иконки
  • button — кнопка с состояниями, умеет перерисовывать себя при нажатии
  • divider — разделитель с размером
  • image — картинка
  • tabs — табы с контентом

Логика страниц

Раньше на BDUI-страницах мы использовали один адаптер и управляли логиков в зависимости от вида экрана:

  • для экрана без табов все просто — достаточно отрисовать список секций и управлять их поведением, например:
    02.png
  • для экрана с табами отрисовывали все секции и запоминали позицию, для которой нужно управлять логикой переключения таба. Затем переключались между страницами и обновляли список на экране:
03.png

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

Мы исследовали различные решения для Recycler View, которые помогли бы нам избежать сложной логики и обратили внимание на технологию Concat Adapter.

Concat Adapter

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

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

Экран без табов

В секции Offer Items Section (указывает на начало пагинации в этом месте) разделяем все элементы по три адаптерам по такой логике:

  • Header adapter — все секции до элемента Offer Items Section
  • Middle adapter — секции из списка с пагинацией Offer Items Section
  • Footer adapter — все секции после элемента Offer Items Section

Теперь при получении новых элементов в пагинационном списке легко отрисовать элементы — нужно просто добавить их в Middle adapter.

04.png
Представим, что нам пришла следующая конфигурация:

[
   1 - {
	   type: header
 },
   2 - {
	   type: section_separator
 	 },
   3 - {
	   type: button
 	 },
   4 - {
	   type: offer_items - секция указывающая на пагинационный список
       }
]

Первые три секции ушли в Header Adapter, а секции, пришедшие из списка с пагинацией, добавились в Middle Adapter.

Экран с табами

В секции Tabs Section (указывает на табы) разделяем все элементы по пяти адаптерам по следующей логике:

  • Header adapter— всё, что идёт до секции Tabs Section, и сам Tabs Section. Далее смотрим на текущий активный таб и в зависимости от его элементов добавляем:
    • Tab Header adapter — все секции до элемента Offer Items Section в этом табе
    • MIDDLE adapter — секции из списка с пагинацией Offer Items Section в этом табе
    • Tab Footer adapter — все секции после элемента Offer Items Section в этом табе
  • Footer adapter — всё, что идет после элемента, указывающего на Tabs Section
05.png

Представим, что нам пришла следующая конфигурация:

[
   1 - {
	   type: header
 },
   2 - {
	   type: button
 	 },
   3 - {
	   type: section_separator
 	 },
   4 - {
	   type: button
 	 },
   5 - {
	   type: tabs,
	   tabs: [
		{
		   title: string,
		   items: [
			5.1 - {
			   type: offer_items - пагинационный список
			}
		   ]
		},
		{
		   title: string,
		   items: [
			5.2 - {
			   type: button
			},
			5.3 - {
			   type: text
			}
		   ]
		}
	   ]
 	 }
]

Первые пять секций уходят в Header Adapter, а Tab Header Adapter и Tab Footer Adapter остаются пустыми. Offer Items Section добавился в Middle Adapter с пагинацией, а секции Button Section и Text Section пока не добавлены ни в один адаптер, поскольку не находятся в активном табе.

При переходе на новый таб перерисовываем контент табов, но при этом элементы Header adapter и Footer adapter не трогаем. Итог выглядит так:

Чтобы вынести логику адаптеров, мы добавили ConcatAdapterHandler. Он предоставляет ConcatAdapter, который мы уже подключаем к RecyclerView.

06.png
Также добавили несколько функций, которые заполняют адаптеры контентом:
07.png
Таким образом, благодаря Concat Adapter у нас получилось реализовать большинство экранов, которые может конфигуровать на бекенде, а также оставить довольно простую логику добавления секций, что позволяет легко в будущем писать код для поддержки новых BDUI секций.

Другие публикации

  • 🎙
  • Доставка
  • Техплатформа
  • Маркет
  • Еда
  • Лавка
  • Такси

На чем держится стабильная работа сервисов Яндекса? / I like techno

  • 📹
  • mobile
  • Маркет

BDUI MythBusters

  • 🔥
  • ✍🏻
  • 📹
  • backend
  • mobile
  • Доставка
  • Еда
  • Лавка
  • Маркет
  • Такси
  • Техплатформа

Как прошла Яндекс Go Dev Day&Night

  • 📹
  • mobile
  • Такси

Yet another Flutter DI