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

MVVM и выбор элементов в адаптере — Базовый адаптер

Время на прочтение4 мин
Количество просмотров2.2K

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


Интерфейс SelectingListAdapter


Начнём с простого интерфейса SelectingListAdapter, который я добавил для адаптера с обычным линейным списком. По моему опыту, где-то 90-95% адаптеров реализуются именно в таком виде.


interface SelectingListAdapter<T> {
    fun setListItems(items: ArrayList<T>)
}

Ничего сложного — только обновление списка элементов. И, на мой взгляд, не составит труда добавить этот интерфейс к списку реализуемых в вашем адаптере. Зачем? Чтобы можно было использовать оба метода расширения, которые я укажу далее:


fun <T> SelectingListAdapter<T>.observeItemsChange(lifecycleOwner: LifecycleOwner,
                                                   liveDataSource: LiveDataSource<T>) {
    liveDataSource.allItems.observe(lifecycleOwner, { items -> setListItems(items) })
}

Метод observeItemsChange подписывает на изменения списка элементов в LiveDataSource.


fun RecyclerView.Adapter<*>.observeSelectionChange(lifecycleOwner: LifecycleOwner,
                                                   liveDataSource: LiveDataSource<*>) {
    liveDataSource.observeSelectionChange(lifecycleOwner) { position, _ ->
        notifyItemChanged(position)
    }
}

Метод observeSelectionChange уже подписывает на изменения в выборе элементов. Здесь происходит только оповещение о том, что элемент надо обновить. О проверке, выбран ли уже этот элемент, будет описано ниже.


fun <T, TAdapter> TAdapter.observeAllChanges(lifecycleOwner: LifecycleOwner,
                                             liveDataSource: LiveDataSource<T>)
        where TAdapter : RecyclerView.Adapter<*>,
              TAdapter : SelectingListAdapter<T> {
    observeSelectionChange(lifecycleOwner, liveDataSource)
    observeItemsChange(lifecycleOwner, liveDataSource)
}

Ну и если хочется вызвать сразу оба метода, есть observeAllChanges, который сделает это одной строкой.


Класс BaseSelectingListAdapter


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


abstract class BaseSelectingListAdapter<T, VH: BaseSelectingListHolder<T>> 
        : RecyclerView.Adapter<VH>(), SelectingListAdapter<T> {
    var callback: SelectingListAdapterCallback? = null
    ...
}
abstract class BaseSelectingListHolder<T>(itemView: View) : RecyclerView.ViewHolder(itemView) {
    abstract fun bindItem(item: T, isSelected: Boolean, onClick: (() -> Unit)?)
}

Пока сосредоточимся на том, что же это за SelectingListAdapterCallback.


interface SelectingListAdapterCallback {
    fun isItemSelected(position: Int): Boolean
    fun clickItem(position: Int)
}

Это весьма простенький колбэк, на котором будет как раз определение, выбран ли запрашиваемый элемент, а так же на него будет перенаправлена обработка клика по элементу.
И в BaseSelectingListAdapter есть метод, чтобы создать этот самый колбэк на основании LiveDataSource.


fun <T> setCallback(liveDataSource: LiveDataSource<T>) {
    callback = object : SelectingListAdapterCallback {
        override fun isItemSelected(position: Int) =
            liveDataSource.isItemSelected(position)
        override fun clickItem(position: Int) {
            liveDataSource.clickPosition(position)
        }
    }
}

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


    fun fullyInitialize(lifecycleOwner: LifecycleOwner,
                        liveDataSource: LiveDataSource<T>) {
        observeAllChanges(lifecycleOwner, liveDataSource)
        setCallback(liveDataSource)
    }

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


В итоге в вашем адаптере остаётся только реализовать метод создания холдера onCreateViewHolder и реализовать метод холдера bindItem. По-моему, получилось избавиться от достаточно большого boilerplate куска.


class MyAdapter : BaseSelectingListAdapter<User, MyHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
        MyHolder(...)//create your holder view
}
class MyHolder(itemView: View) : BaseSelectingListHolder<User>(itemView) {
    override fun bindItem(item: User, isSelected: Boolean, onClick: (() -> Unit)?) {
        //bind your data
    }
}

Перспективы


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


  1. Потокобезопасность.
  2. Изменение типа выбора элементов (с одиночного на множественный, например), то есть подмена SelectionManager'а.

Сылки


Исходные коды для библиотек:



Ссылки в Gradle:


implementation 'ru.ircover.selectionmanager:core:1.1.0'
implementation 'ru.ircover.selectionmanager:livesource:1.0.1'
implementation 'ru.ircover.selectionmanager:selectingadapter:1.0.0'

Теги:
Хабы:
Всего голосов 3: ↑3 и ↓0+3
Комментарии2

Публикации