Remove usage of BindingCollectionAdapter (part 1)

This commit is contained in:
topjohnwu 2022-06-02 20:55:19 -07:00
parent 2ed092c9db
commit 6305159c5e
25 changed files with 166 additions and 167 deletions

View File

@ -81,7 +81,6 @@ dependencies {
val vBAdapt = "4.0.0"
val bindingAdapter = "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter"
implementation("${bindingAdapter}:${vBAdapt}")
implementation("${bindingAdapter}-recyclerview:${vBAdapt}")
val vLibsu = "5.0.2"
implementation("com.github.topjohnwu.libsu:core:${vLibsu}")

View File

@ -1,13 +0,0 @@
package com.topjohnwu.magisk.databinding
import androidx.databinding.ViewDataBinding
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
open class BindingBoundAdapter : BindingRecyclerViewAdapter<RvItem>() {
override fun onBindBinding(binding: ViewDataBinding, variableId: Int, layoutRes: Int, position: Int, item: RvItem) {
super.onBindBinding(binding, variableId, layoutRes, position, item)
item.onBindingBound(binding)
}
}

View File

@ -1,35 +1,7 @@
package com.topjohnwu.magisk.databinding
import androidx.databinding.ViewDataBinding
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
import me.tatarka.bindingcollectionadapter2.ItemBinding
import me.tatarka.bindingcollectionadapter2.OnItemBind
fun <T : AnyDiffRvItem> diffListOf() =
DiffObservableList(DiffRvItem.callback<T>())
fun <T : AnyDiffRvItem> diffListOf(newItems: List<T>) =
DiffObservableList(DiffRvItem.callback<T>()).also { it.update(newItems) }
fun <T : AnyDiffRvItem> filterableListOf() =
FilterableDiffObservableList(DiffRvItem.callback<T>())
fun <T : RvItem> adapterOf() = object : BindingRecyclerViewAdapter<T>() {
override fun onBindBinding(
binding: ViewDataBinding,
variableId: Int,
layoutRes: Int,
position: Int,
item: T
) {
super.onBindBinding(binding, variableId, layoutRes, position, item)
item.onBindingBound(binding)
}
}
inline fun <T : RvItem> itemBindingOf(
crossinline body: (ItemBinding<*>) -> Unit = {}
) = OnItemBind<T> { itemBinding, _, item ->
item.bind(itemBinding)
body(itemBinding)
}

View File

@ -1,33 +1,21 @@
package com.topjohnwu.magisk.databinding
import androidx.annotation.CallSuper
import androidx.databinding.PropertyChangeRegistry
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.BR
import me.tatarka.bindingcollectionadapter2.ItemBinding
abstract class RvItem {
abstract val layoutRes: Int
@CallSuper
open fun bind(binding: ItemBinding<*>) {
binding.set(BR.item, layoutRes)
}
/**
* This callback is useful if you want to manipulate your views directly.
* If you want to use this callback, you must set [me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter]
* on your RecyclerView and call it from there. You can use [BindingBoundAdapter] for your convenience.
*/
open fun onBindingBound(binding: ViewDataBinding) {}
}
interface RvContainer<E> {
val item: E
}
interface ViewAwareRvItem {
fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView)
}
interface ComparableRv<T> : Comparable<T> {
@Suppress("UNCHECKED_CAST")
fun comparableEqual(o: Any?) =
@ -77,13 +65,3 @@ abstract class ObservableDiffRvItem<T> : DiffRvItem<T>(), ObservableHost {
abstract class ObservableRvItem : RvItem(), ObservableHost {
override var callbacks: PropertyChangeRegistry? = null
}
/**
* This item addresses issues where enclosing recycler has to be invalidated or generally
* manipulated with. This shouldn't be however necessary for 99.9% of use-cases. Refrain from using
* this item as it provides virtually no additional functionality. Stick with ComparableRvItem.
* */
interface LenientRvItem {
fun onBindingBound(binding: ViewDataBinding, recyclerView: RecyclerView)
}

View File

@ -1,35 +0,0 @@
package com.topjohnwu.magisk.databinding
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
class RvBindingAdapter<T : RvItem> : BindingRecyclerViewAdapter<T>() {
private var recyclerView: RecyclerView? = null
override fun onBindBinding(
binding: ViewDataBinding,
variableId: Int,
layoutRes: Int,
position: Int,
item: T
) {
super.onBindBinding(binding, variableId, layoutRes, position, item)
when (item) {
is LenientRvItem -> {
val recycler = recyclerView ?: return
item.onBindingBound(binding)
item.onBindingBound(binding, recycler)
}
else -> item.onBindingBound(binding)
}
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
this.recyclerView = recyclerView
}
}

View File

@ -0,0 +1,118 @@
package com.topjohnwu.magisk.databinding
import android.annotation.SuppressLint
import android.util.SparseArray
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.BindingAdapter
import androidx.databinding.DataBindingUtil
import androidx.databinding.ObservableList
import androidx.databinding.ObservableList.OnListChangedCallback
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.BR
class RvItemAdapter<T: RvItem>(
private val items: List<T>,
private val extraBindings: SparseArray<*>?
) : RecyclerView.Adapter<RvItemAdapter.ViewHolder>() {
private var lifecycleOwner: LifecycleOwner? = null
private var recyclerView: RecyclerView? = null
private val observer by lazy(LazyThreadSafetyMode.NONE) { ListObserver<T>() }
override fun onAttachedToRecyclerView(rv: RecyclerView) {
lifecycleOwner = rv.findViewTreeLifecycleOwner()
recyclerView = rv
if (items is ObservableList)
items.addOnListChangedCallback(observer)
}
override fun onDetachedFromRecyclerView(rv: RecyclerView) {
lifecycleOwner = null
recyclerView = null
if (items is ObservableList)
items.removeOnListChangedCallback(observer)
}
override fun onCreateViewHolder(parent: ViewGroup, layoutRes: Int): ViewHolder {
val inflator = LayoutInflater.from(parent.context)
return ViewHolder(DataBindingUtil.inflate(inflator, layoutRes, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
holder.binding.setVariable(BR.item, item)
extraBindings?.let {
for (i in 0 until it.size()) {
holder.binding.setVariable(it.keyAt(i), it.valueAt(i))
}
}
holder.binding.lifecycleOwner = lifecycleOwner
holder.binding.executePendingBindings()
recyclerView?.let {
if (item is ViewAwareRvItem)
item.onBind(holder.binding, it)
}
}
override fun getItemCount() = items.size
override fun getItemViewType(position: Int) = items[position].layoutRes
class ViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root)
inner class ListObserver<T: RvItem> : OnListChangedCallback<ObservableList<T>>() {
@SuppressLint("NotifyDataSetChanged")
override fun onChanged(sender: ObservableList<T>) {
notifyDataSetChanged()
}
override fun onItemRangeChanged(
sender: ObservableList<T>,
positionStart: Int,
itemCount: Int
) {
notifyItemRangeChanged(positionStart, itemCount)
}
override fun onItemRangeInserted(
sender: ObservableList<T>?,
positionStart: Int,
itemCount: Int
) {
notifyItemRangeInserted(positionStart, itemCount)
}
override fun onItemRangeMoved(
sender: ObservableList<T>?,
fromPosition: Int,
toPosition: Int,
itemCount: Int
) {
for (i in 0 until itemCount) {
notifyItemMoved(fromPosition + i, toPosition + i)
}
}
override fun onItemRangeRemoved(
sender: ObservableList<T>?,
positionStart: Int,
itemCount: Int
) {
notifyItemRangeRemoved(positionStart, itemCount)
}
}
}
inline fun bindExtra(body: (SparseArray<Any?>) -> Unit) = SparseArray<Any?>().also(body)
@BindingAdapter("items", "extraBindings", requireAll = false)
fun <T: RvItem> RecyclerView.setAdapter(items: List<T>?, extraBindings: SparseArray<*>?) {
if (items != null) {
adapter = RvItemAdapter(items, extraBindings)
}
}

View File

@ -6,8 +6,8 @@ import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.filterableListOf
import com.topjohnwu.magisk.databinding.itemBindingOf
import com.topjohnwu.magisk.ktx.concurrentMap
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
@ -39,11 +39,8 @@ class DenyListViewModel : BaseViewModel() {
}
val items = filterableListOf<DenyListRvItem>()
val itemBinding = itemBindingOf<DenyListRvItem> {
it.bindExtra(BR.viewModel, this)
}
val itemInternalBinding = itemBindingOf<ProcessRvItem> {
it.bindExtra(BR.viewModel, this)
val extraBindings = bindExtra {
it.put(BR.viewModel, this)
}
@SuppressLint("InlinedApi")

View File

@ -7,19 +7,18 @@ import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.DiffRvItem
import com.topjohnwu.magisk.databinding.LenientRvItem
import com.topjohnwu.magisk.databinding.RvContainer
import com.topjohnwu.magisk.databinding.ViewAwareRvItem
import kotlin.math.max
class ConsoleItem(
override val item: String
) : DiffRvItem<ConsoleItem>(), LenientRvItem,
RvContainer<String> {
) : DiffRvItem<ConsoleItem>(), ViewAwareRvItem, RvContainer<String> {
override val layoutRes = R.layout.item_console_md2
private var parentWidth = -1
override fun onBindingBound(binding: ViewDataBinding, recyclerView: RecyclerView) {
override fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView) {
if (parentWidth < 0)
parentWidth = (recyclerView.parent as View).width

View File

@ -14,9 +14,7 @@ import com.topjohnwu.magisk.core.tasks.FlashZip
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.databinding.RvBindingAdapter
import com.topjohnwu.magisk.databinding.diffListOf
import com.topjohnwu.magisk.databinding.itemBindingOf
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.ktx.reboot
@ -36,9 +34,7 @@ class FlashViewModel : BaseViewModel() {
private val _subtitle = MutableLiveData(R.string.flashing)
val subtitle get() = _subtitle as LiveData<Int>
val adapter = RvBindingAdapter<ConsoleItem>()
val items = diffListOf<ConsoleItem>()
val itemBinding = itemBindingOf<ConsoleItem>()
lateinit var args: FlashFragmentArgs
private val logItems = mutableListOf<String>().synchronized()

View File

@ -12,7 +12,7 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.core.download.Subject.App
import com.topjohnwu.magisk.core.repository.NetworkService
import com.topjohnwu.magisk.databinding.itemBindingOf
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.EnvFixDialog
@ -75,8 +75,8 @@ class HomeViewModel(
var stateManagerProgress = 0
set(value) = set(value, field, { field = it }, BR.stateManagerProgress)
val itemBinding = itemBindingOf<IconLink> {
it.bindExtra(BR.viewModel, this)
val extraBindings = bindExtra {
it.put(BR.viewModel, this)
}
companion object {

View File

@ -10,8 +10,8 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.repository.LogRepository
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.diffListOf
import com.topjohnwu.magisk.databinding.itemBindingOf
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.ktx.timeFormatStandard
@ -34,8 +34,8 @@ class LogViewModel(
// --- su log
val items = diffListOf<LogRvItem>()
val itemBinding = itemBindingOf<LogRvItem> {
it.bindExtra(BR.viewModel, this)
val extraBindings = bindExtra {
it.put(BR.viewModel, this)
}
// --- magisk log

View File

@ -8,8 +8,6 @@ import com.topjohnwu.magisk.arch.BaseFragment
import com.topjohnwu.magisk.arch.viewModel
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding
import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.adapterOf
import rikka.recyclerview.addEdgeSpacing
import rikka.recyclerview.addInvalidateItemDecorationsObserver
import rikka.recyclerview.addItemSpacing
@ -34,7 +32,6 @@ class ModuleFragment : BaseFragment<FragmentModuleMd2Binding>() {
super.onViewCreated(view, savedInstanceState)
binding.moduleList.apply {
adapter = adapterOf<RvItem>()
addEdgeSpacing(top = R.dimen.l_50, bottom = R.dimen.l1)
addItemSpacing(R.dimen.l1, R.dimen.l_50, R.dimen.l1)
fixEdgeEffect()

View File

@ -11,8 +11,8 @@ import com.topjohnwu.magisk.core.base.ContentResultCallback
import com.topjohnwu.magisk.core.model.module.LocalModule
import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.diffListOf
import com.topjohnwu.magisk.databinding.itemBindingOf
import com.topjohnwu.magisk.events.GetContentEvent
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.ModuleInstallDialog
@ -30,8 +30,8 @@ class ModuleViewModel : BaseViewModel() {
private val itemsInstalled = diffListOf<LocalModuleRvItem>()
val items = MergeObservableList<RvItem>()
val itemBinding = itemBindingOf<RvItem> {
it.bindExtra(BR.viewModel, this)
val extraBindings = bindExtra {
it.put(BR.viewModel, this)
}
val data get() = uri

View File

@ -14,8 +14,7 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.databinding.adapterOf
import com.topjohnwu.magisk.databinding.itemBindingOf
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.events.AddHomeIconEvent
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.BiometricEvent
@ -26,9 +25,10 @@ import kotlinx.coroutines.launch
class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
val adapter = adapterOf<BaseSettingsItem>()
val itemBinding = itemBindingOf<BaseSettingsItem> { it.bindExtra(BR.handler, this) }
val items = createItems()
val extraBindings = bindExtra {
it.put(BR.handler, this)
}
init {
viewModelScope.launch {

View File

@ -5,9 +5,7 @@ import android.view.View
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseFragment
import com.topjohnwu.magisk.arch.viewModel
import com.topjohnwu.magisk.databinding.AnyDiffRvItem
import com.topjohnwu.magisk.databinding.FragmentSuperuserMd2Binding
import com.topjohnwu.magisk.databinding.adapterOf
import rikka.recyclerview.addEdgeSpacing
import rikka.recyclerview.addItemSpacing
import rikka.recyclerview.fixEdgeEffect
@ -26,7 +24,6 @@ class SuperuserFragment : BaseFragment<FragmentSuperuserMd2Binding>() {
super.onViewCreated(view, savedInstanceState)
binding.superuserList.apply {
adapter = adapterOf<AnyDiffRvItem>()
addEdgeSpacing(top = R.dimen.l_50, bottom = R.dimen.l1)
addItemSpacing(R.dimen.l1, R.dimen.l_50, R.dimen.l1)
fixEdgeEffect()

View File

@ -15,8 +15,8 @@ import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.utils.BiometricHelper
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.databinding.AnyDiffRvItem
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.diffListOf
import com.topjohnwu.magisk.databinding.itemBindingOf
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.BiometricEvent
import com.topjohnwu.magisk.events.dialog.SuperuserRevokeDialog
@ -35,14 +35,14 @@ class SuperuserViewModel(
private val itemNoData = TextItem(R.string.superuser_policy_none)
private val itemsPolicies = diffListOf<PolicyRvItem>()
private val itemsHelpers = ObservableArrayList<TextItem>()
private val itemsPolicies = diffListOf<PolicyRvItem>()
val items = MergeObservableList<AnyDiffRvItem>()
.insertList(itemsHelpers)
.insertList(itemsPolicies)
val itemBinding = itemBindingOf<AnyDiffRvItem> {
it.bindExtra(BR.listener, this)
val extraBindings = bindExtra {
it.put(BR.listener, this)
}
// ---

View File

@ -23,8 +23,6 @@ import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.*
import com.topjohnwu.magisk.view.MagiskDialog.DialogClickListener
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapters
import me.tatarka.bindingcollectionadapter2.ItemBinding
typealias DialogButtonClickListener = (DialogInterface) -> Unit
@ -181,14 +179,13 @@ class MagiskDialog(
it.layoutManager = LinearLayoutManager(context)
val items = list.mapIndexed { i, cs -> DialogItem(cs, i) }
val binding = itemBindingOf<DialogItem> { item ->
item.bindExtra(BR.listener, DialogClickListener { pos ->
val extraBindings = bindExtra { sa ->
sa.put(BR.listener, DialogClickListener { pos ->
listener.onClick(pos)
dismiss()
})
}.let { b -> ItemBinding.of(b) }
BindingRecyclerViewAdapters.setAdapter(it, binding, items, null, null, null, null)
}
it.setAdapter(items, extraBindings)
}
)

View File

@ -17,9 +17,6 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/app_list"
invisibleUnless="@{viewModel.loaded}"
itemBinding="@{viewModel.itemBinding}"
items="@{viewModel.items}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
@ -27,6 +24,9 @@
android:paddingTop="@dimen/internal_action_bar_size"
app:fitsSystemWindowsInsets="top|bottom"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:invisibleUnless="@{viewModel.loaded}"
app:items="@{viewModel.items}"
app:extraBindings="@{viewModel.extraBindings}"
tools:listitem="@layout/item_hide_md2"
tools:paddingTop="40dp" />

View File

@ -24,9 +24,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/flash_content"
adapter="@{viewModel.adapter}"
itemBinding="@{viewModel.itemBinding}"
items="@{viewModel.items}"
app:items="@{viewModel.items}"
scrollToLast="@{true}"
android:layout_width="wrap_content"
android:layout_height="match_parent"

View File

@ -20,8 +20,8 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/module_list"
gone="@{viewModel.loading}"
itemBinding="@{viewModel.itemBinding}"
items="@{viewModel.items}"
app:items="@{viewModel.items}"
app:extraBindings="@{viewModel.extraBindings}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"

View File

@ -14,9 +14,8 @@
</data>
<androidx.recyclerview.widget.RecyclerView
adapter="@{viewModel.adapter}"
itemBinding="@{viewModel.itemBinding}"
items="@{viewModel.items}"
app:items="@{viewModel.items}"
app:extraBindings="@{viewModel.extraBindings}"
android:id="@+id/settings_list"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -20,8 +20,8 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/superuser_list"
goneUnless="@{viewModel.loaded || !viewModel.items.empty}"
itemBinding="@{viewModel.itemBinding}"
items="@{viewModel.items}"
app:items="@{viewModel.items}"
app:extraBindings="@{viewModel.extraBindings}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"

View File

@ -17,8 +17,8 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/log_superuser"
itemBinding="@{viewModel.itemBinding}"
items="@{viewModel.items}"
app:items="@{viewModel.items}"
app:extraBindings="@{viewModel.extraBindings}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"

View File

@ -32,9 +32,9 @@
tools:text="\@topjohnwu" />
<androidx.recyclerview.widget.RecyclerView
itemBinding="@{viewModel.itemBinding}"
items="@{item.items}"
nestedScrollingEnabled="@{false}"
app:items="@{item.items}"
app:extraBindings="@{viewModel.extraBindings}"
app:nestedScrollingEnabled="@{false}"
android:overScrollMode="never"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -95,8 +95,8 @@
<androidx.recyclerview.widget.RecyclerView
goneUnless="@{item.isExpanded}"
itemBinding="@{viewModel.itemInternalBinding}"
items="@{item.processes}"
app:items="@{item.processes}"
app:extraBindings="@{viewModel.extraBindings}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorSurfaceVariant"