Динамическая генерация UI в Android приложении при помощи RecyclerView и AdapterDelegates

Roman Kryvolapov
3 min readApr 24, 2023

Привет!))

Постараюсь рассказать об опыте использования динамической генерации UI в Android приложении при помощи RecyclerView и библиотеки https://github.com/sockeqwe/AdapterDelegates

Есть разные способы работы с UI в Android приложении- например, вы можете сделать UI статическим, либо сгенерировать при помощи Jetpack Compose, а можно выбрать и некий промежуточный вариант, где элементы будут переиспользуемы на уровне кода, а для их отображения будет использоваться RecyclerView. Сначала добавляем библиотеку по соответствующей инструкции с ее страницы.

В библиотеке есть класс AdapterDelegat, в котором есть метод isForViewType, он возвращает, относится ли элемент в списке к указанному классу. Сначала создаем интерфейс, который будет относиться к определенной странице приложения и наследовать класс DiffEquals

interface DiffEquals {
fun isItemSame(other: Any?): Boolean
fun isContentSame(other: Any?): Boolean
}

interface AccountAdapterMarker : DiffEquals

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

и для каждого уникального элемента в списке создаем класс, где будут храниться его данные, и будет некая логика для сравнения, аналогичная DiffUtil.ItemCallback

data class AccountName(
val accountName: String,
) : AccountAdapterMarker {

override fun isItemSame(other: Any?): Boolean {
return // логика сравнения
}

override fun isContentSame(other: Any?): Boolean {
return // логика сравнения
}

далее создаем делегат для этого класса, наследующийся от класса библиотеки AdapterDelegate с дженериком интерфейса

class AccountNameDelegate @Inject constructor() :
AdapterDelegate<MutableList<AccountAdapterMarker>>() {

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

var accontNameClickListener: ((accountName: AccountName) -> Unit)? = null

override fun isForViewType(items: MutableList<CommonTitleAdapterMarker>, position: Int): Boolean {
return items[position] is AccountName
}

override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder {
return ViewHolder(parent.inflateBinding(ListItemAccountNameBinding::inflate))
}

override fun onBindViewHolder(
items: MutableList<AccountAdapterMarker>,
position: Int,
holder: RecyclerView.ViewHolder,
payloads: MutableList<Any>
) {
(holder as ViewHolder).bind(items[position] as AccountName)
}

private inner class ViewHolder(
private val binding: ListItemAccountNameBinding,
) : RecyclerView.ViewHolder(binding.root) {

fun bind(accountName: AccountName) {
binding.tvAccountName.text = accountName.accountName
binding.tvAccountName.setOnClickListener{
accontNameClickListener?.invoke(accountName) }
}
}
}

соответственно, делаем такой делегат для каждого элемента, который должен присутствовать на странице. Далее, делаем адаптер, в который добавляем делегаты этих элементов, также с дженериком интерфейса страницы приложения

class AccountAdapter @Inject constructor(
private val accountNameDelegate: AccountNameDelegate,
) : AsyncListDifferDelegationAdapter<AccountAdapterMarker>(DefaultDiffUtilCallback()) {

var accountClickListener: AccountClickListener? = null
set(value) {
field = value
accountNameDelegate.accontNameClickListener = { field?.onAccountNameClicked(it) }
}

init {
items = mutableListOf()
@Suppress("UNCHECKED_CAST")
delegatesManager.apply {
addDelegate(
// каст нужен только если исопльзуем super интерфейс
// для переиспользования элементов, а этом примере
// он не обязателен
accountNameDelegate as AdapterDelegate<MutableList<AccountAdapterMarker>>
)

}
}

// наследуем этот интерфейс во фрагменте,
// чтобы обрабатывать нажатия и другие действия
// и прописываем во фрагменте
// accountAdapter.accountClickListener = this
interface AccountClickListener{
fun onAccountNameClicked(accountName: AccountName)
}
}

ну а далее нужно во ViewModel или специальном маппере создать список с экземплярами классов, наследующими AccountAdapterMarker, и вызвать у адаптера метод setItems, а адаптера уже разребется, инстансы какого класса в нем находятся, и добавит эти элементы в RecyclerView.

Надеюсь, было понятно, если найдете ошибку или есть вопросы- пишите на
Telegram @RomanKryvolapov или
roman.kryvolapov@gmail.com или
https://www.linkedin.com/in/roman-kryvolapov/

--

--