---
title: "Динамічна генерація UI в Android застосунку за допомогою RecyclerView та AdapterDelegates"
url: "https://romankryvolapov.com/uk/recyclerview-adapterdelegates/"
description: "Як зібрати динамічний UI на RecyclerView з бібліотекою AdapterDelegates: делегати, перевикористання елементів списку та порівняння через DiffEquals."
language: uk
updated: 2026-01-21
---
Спробую розповісти про досвід використання динамічної генерації UI в Android застосунку за допомогою RecyclerView та бібліотеки

[https://github.com/sockeqwe/AdapterDelegates](https://github.com/sockeqwe/AdapterDelegates)

Є різні способи роботи з UI в Android застосунку — наприклад, ви можете зробити UI статичним, або згенерувати за допомогою Jetpack Compose, а можна обрати і певний проміжний варіант, де елементи будуть перевикористовуваними на рівні коду, а для їх відображення використовуватиметься RecyclerView. Спочатку додаємо бібліотеку за відповідною інструкцією з її сторінки.

## Створення інтерфейсів та класів

У бібліотеці є клас AdapterDelegat, у якому є метод isForViewType, він повертає, чи належить елемент у списку до вказаного класу. Спочатку створюємо інтерфейс, який належатиме до певної сторінки застосунку та успадковуватиме клас DiffEquals

Якщо елемент необхідно перевикористати, створюємо super інтерфейс, який успадковуватиме всі інтерфейси сторінок, на яких має бути цей елемент, а в делегаті як дженерик використовуємо цей super інтерфейс, а не інтерфейс сторінки застосунку

Для кожного унікального елемента у списку створюємо клас, де зберігатимуться його дані, і буде певна логіка для порівняння, аналогічна DiffUtil.ItemCallback

```kotlin
interface DiffEquals {


    fun isItemSame(other: Any?): Boolean


    fun isContentSame(other: Any?): Boolean


}
```

```kotlin
fun interface DeepCopy<T> {

    fun deepCopy(): T

}
```

```kotlin
@Suppress("UNCHECKED_CAST")
inline fun <reified T> T.equalTo(other: Any?, vararg properties: T.() -> Any?): Boolean {
    if (other !is T) return false
    properties.forEach {
        val currentValue = it.invoke(this)
        val otherValue = it.invoke(other)
        if (currentValue is List<*>) {
            if (otherValue !is List<*> || !currentValue.isListContentEqualsOrSame(otherValue)) {
                return false
            }
        } else if (it.invoke(this) != it.invoke(other)) {
            return false
        }
    }
    return true
}

/**
 * Compare two lists by their content.
 * The entity of this list must have a DiffEquals implementation.
 * @return true when same, false otherwise
 */
fun <T : DiffEquals> List<T>.isListContentSame(other: List<T>): Boolean {
    if (size != other.size) return false
    withIndex().forEach {
        if (!it.value.isContentSame(other[it.index])) {
            return false
        }
    }
    return true
}

/**
 * Compare two lists by their content.
 * This method uses a default equal operation to compare list items
 * @return true when same, false otherwise
 */
fun List<*>.isListContentEquals(other: List<*>): Boolean {
    if (size != other.size) return false
    withIndex().forEach {
        if (it.value != other[it.index]) {
            return false
        }
    }
    return true
}

/**
 * Compare two lists by their content.
 * This method uses a default equal operation to compare list items
 * or the diff equals mechanic if the list item is implements [DiffEquals]
 * @return true when same, false otherwise
 */
fun List<*>.isListContentEqualsOrSame(other: List<*>): Boolean {
    if (size != other.size) return false
    withIndex().forEach {
        if (it.value is DiffEquals && other[it.index] is DiffEquals) {
            if (!(it.value as DiffEquals).isContentSame(other[it.index])) {
                return false
            }
        } else if (it.value != other[it.index]) {
            return false
        }
    }
    return true
}

/**
 * Makes a deep copy of the list. The items in the list should implement
 * [DeepCopy] interface.
 */
fun <T : DeepCopy<T>> List<T>.deepCopy(): List<T> {
    val oldList = this
    return mutableListOf<T>().apply {
        oldList.forEach {
            add(it.deepCopy())
        }
    }
}

fun <T> List<T>.moveItemToFirstPosition(predicate: (T) -> Boolean): List<T> {
    for (element in this.withIndex()) {
        if (predicate(element.value)) {
            return this.toMutableList().apply {
                removeAt(element.index)
                add(0, element.value)
            }.toList()
        }
    }
    return this
}
```

```kotlin
interface AccountAdapterMarker : DiffEquals
```

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


	override fun isItemSame(other: Any?): Boolean {
        return equalTo(
            other,
            { accountName },
        )
    }


    override fun isContentSame(other: Any?): Boolean {
        return equalTo(
            other,
            { accountName },
        )
    }


}
```

## Створення делегата

далі створюємо делегат для цього класу, що успадковується від класу бібліотеки AdapterDelegate з дженериком інтерфейсу

```kotlin
class AccountNameDelegate() : AdapterDelegate<MutableList<AccountAdapterMarker>>() {


  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) }
    }
  }

}
```

## Створення адаптера

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

```kotlin
class AccountAdapter (
  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.

[ Copyright: Roman Kryvolapov ](https://t.me/RomanKryvolapov)
