Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions adapt/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ dependencies {
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

// Paging 3
implementation("androidx.paging:paging-runtime:3.2.1")

// Lifecycle (if not already present)
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.3")
implementation("androidx.lifecycle:lifecycle-common-java8:2.8.3")
}

nmcp {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package io.github.vshnv.adapt.adapter

import android.widget.Filterable
import androidx.recyclerview.widget.RecyclerView

abstract class AdaptAdapter<T>: RecyclerView.Adapter<AdaptViewHolder<T>>() {
abstract class AdaptAdapter<T>: RecyclerView.Adapter<AdaptViewHolder<T>>(), Filterable {
abstract val currentList: List<T>
abstract fun getUnfilteredList(): List<T>

abstract suspend fun submitDataSuspending(data: List<T>): Unit

abstract fun submitData(data: List<T>, callback: () -> Unit = {}): Unit
abstract suspend fun submitDataSuspending(data: List<T>)
abstract fun submitData(data: List<T>, callback: () -> Unit = {})
abstract fun submitDataFromFilter(data: List<T>, callback: () -> Unit = {})
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import android.view.View
import androidx.recyclerview.widget.RecyclerView

abstract class AdaptViewHolder<T>(view: View): RecyclerView.ViewHolder(view) {
abstract fun bind(idx: Int, data: T): Unit
abstract fun bind(idx: Int, data: T)
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
package io.github.vshnv.adapt.adapter

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.OnLifecycleEvent
import java.lang.ref.WeakReference

class AdapterLifecycleRegistry(owner: LifecycleOwner, private val parentLifecycle: Lifecycle): LifecycleRegistry(owner) {
private val ownerWeakRef = WeakReference(owner)
private val parentLifecycleObserver = object: LifecycleObserver {
@OnLifecycleEvent(Event.ON_ANY)
fun onAny() {
if (ownerWeakRef.get() == null) {
ignoreParent()
return
}
private val parentLifecycleObserver = LifecycleEventObserver { _, _ ->
if (ownerWeakRef.get() == null) {
ignoreParent()
} else {
currentState = parentLifecycle.currentState
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@ package io.github.vshnv.adapt.adapter

import android.view.View
import android.view.ViewGroup
import android.widget.Filter
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import io.github.vshnv.adapt.dsl.collector.CollectingBindable
import io.github.vshnv.adapt.extensions.findViewTreeLifecycleOwner
import java.util.Collections
import java.util.WeakHashMap
import kotlin.coroutines.suspendCoroutine

class LifecycleAwareAdaptAdapter<T : Any>(private val viewTypeMapper: ((T, Int) -> Int)?, private val defaultBinder: CollectingBindable<T, *>?, private val viewBinders: MutableMap<Int, CollectingBindable<T, *>>, private val itemEquals: (T, T) -> Boolean, private val itemContentEquals: (T, T) -> Boolean): AdaptAdapter<T>() {
class LifecycleAwareAdaptAdapter<T : Any>(
private val viewTypeMapper: ((T, Int) -> Int)?,
private val defaultBinder: CollectingBindable<T, *>?,
private val viewBinders: MutableMap<Int, CollectingBindable<T, *>>,
private val itemEquals: (T, T) -> Boolean,
private val itemContentEquals: (T, T) -> Boolean,
private var searchFilter: Filter?,
) : AdaptAdapter<T>() {
private val knownAffectedViewHolders = Collections.newSetFromMap(WeakHashMap<LifecycleAwareAdaptViewHolder<T>, Boolean>())
private val diffCallback: DiffUtil.ItemCallback<T> = object : DiffUtil.ItemCallback<T>() {
override fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
Expand All @@ -28,12 +36,20 @@ class LifecycleAwareAdaptAdapter<T : Any>(private val viewTypeMapper: ((T, Int)
private val mDiffer: AsyncListDiffer<T> = AsyncListDiffer(this, diffCallback)
override val currentList: List<T>
get() = mDiffer.currentList
private var unFilteredList: MutableList<T> = mutableListOf()

override fun getFilter(): Filter = requireNotNull(searchFilter) {
"Filterable.Filter of $this accessed before assigning"
}

override fun getUnfilteredList(): List<T> = unFilteredList

override fun getItemViewType(position: Int): Int {
return viewTypeMapper?.let {
it(getItem(position), position)
} ?: super.getItemViewType(position)
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AdaptViewHolder<T> {
val binderItem: CollectingBindable<T, *> = viewBinders[viewType] ?: defaultBinder
?: throw AssertionError("Adapt found ViewType with no bound view creator or any default view creator, Cannot proceed!")
Expand Down Expand Up @@ -61,13 +77,21 @@ class LifecycleAwareAdaptAdapter<T : Any>(private val viewTypeMapper: ((T, Int)
return mDiffer.currentList[position]
}

override suspend fun submitDataSuspending(data: List<T>) = suspendCoroutine<Unit> { continuation ->
mDiffer.submitList(data) {
continuation.resumeWith(Result.success(Unit))
override suspend fun submitDataSuspending(data: List<T>) {
unFilteredList = data.toMutableList()
suspendCoroutine<Unit> { continuation ->
mDiffer.submitList(data) {
continuation.resumeWith(Result.success(Unit))
}
}
}

override fun submitData(data: List<T>, callback: () -> Unit) {
unFilteredList = data.toMutableList()
mDiffer.submitList(data, callback)
}

override fun submitDataFromFilter(data: List<T>, callback: () -> Unit) {
mDiffer.submitList(data, callback)
}

Expand All @@ -81,12 +105,13 @@ class LifecycleAwareAdaptAdapter<T : Any>(private val viewTypeMapper: ((T, Int)
override fun onViewAttachedToWindow(holder: AdaptViewHolder<T>) {
super.onViewAttachedToWindow(holder)
val holder = (holder as LifecycleAwareAdaptViewHolder<T>)
val lifecycleOwner = holder.itemView.findViewTreeLifecycleOwner() ?: return
val lifecycleOwner = ViewTreeLifecycleOwner.get(holder.itemView) ?: return
holder.handleLifecycleSetup(lifecycleOwner)
val registry = holder.lifecycleRegistry
registry?.highestState = Lifecycle.State.RESUMED
knownAffectedViewHolders.add(holder)
}

override fun onViewDetachedFromWindow(holder: AdaptViewHolder<T>) {
val registry = (holder as LifecycleAwareAdaptViewHolder<T>).lifecycleRegistry
registry?.highestState = Lifecycle.State.CREATED
Expand Down
2 changes: 2 additions & 0 deletions adapt/src/main/java/io/github/vshnv/adapt/dsl/AdaptScope.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package io.github.vshnv.adapt.dsl

import android.view.ViewGroup
import android.widget.Filter

interface AdaptScope<T: Any> {
fun itemEquals(checkEquality: (data: T, otherData: T) -> Boolean)
fun contentEquals(checkContentEquality: (data: T, otherData: T) -> Boolean)
fun defineViewTypes(mapToViewType: (data: T, position: Int) -> Int)
fun filter(searchFilter: Filter?)
fun <V: Any> create(createView: (parent: ViewGroup) -> ViewSource<V>): Bindable<T, V>
fun <V: Any> create(viewType: Int, createView: (parent: ViewGroup) -> ViewSource<V>): Bindable<T, V>
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package io.github.vshnv.adapt.dsl.collector

import android.view.ViewGroup
import android.widget.Filter
import io.github.vshnv.adapt.adapter.AdaptAdapter
import io.github.vshnv.adapt.adapter.LifecycleAwareAdaptAdapter
import io.github.vshnv.adapt.dsl.AdaptScope
import io.github.vshnv.adapt.dsl.Bindable
import io.github.vshnv.adapt.dsl.ViewSource

internal class CollectingAdaptScope<T: Any>: AdaptScope<T> {
private var itemEquals: (T, T) -> Boolean = {a, b -> a == b}
private var itemContentEquals: (T, T) -> Boolean = {a, b -> a == b}
internal class CollectingAdaptScope<T : Any> : AdaptScope<T> {
private var itemEquals: (T, T) -> Boolean = { a, b -> a == b }
private var itemContentEquals: (T, T) -> Boolean = { a, b -> a == b }
private var viewTypeMapper: ((T, Int) -> Int)? = null
private var defaultBinder: CollectingBindable<T, *>? = null
private var defaultBinder: CollectingBindable<T, *>? = null
private val viewBinders: MutableMap<Int, CollectingBindable<T, *>> = mutableMapOf()
private var searchFilterable: Filter? = null

override fun defineViewTypes(mapToViewType: (T, Int) -> Int) {
viewTypeMapper = mapToViewType
Expand All @@ -26,25 +28,30 @@ internal class CollectingAdaptScope<T: Any>: AdaptScope<T> {
itemContentEquals = checkContentEquality
}

override fun filter(searchFilter: Filter?) {
searchFilterable = searchFilter
}

internal fun buildAdapter(): AdaptAdapter<T> {
return LifecycleAwareAdaptAdapter<T>(
viewTypeMapper,
defaultBinder,
viewBinders,
itemEquals,
itemContentEquals
itemContentEquals,
searchFilterable
)
}

override fun <V: Any> create(createView: (parent: ViewGroup) -> ViewSource<V>): Bindable<T, V> {
override fun <V : Any> create(createView: (parent: ViewGroup) -> ViewSource<V>): Bindable<T, V> {
return CollectingBindable<T, V>(createView).apply {
defaultBinder = this
}
}

override fun <V: Any> create(
override fun <V : Any> create(
viewType: Int,
createView: (parent: ViewGroup) -> ViewSource<V>
createView: (parent: ViewGroup) -> ViewSource<V>,
): Bindable<T, V> {
return CollectingBindable<T, V>(createView).apply {
viewBinders[viewType] = this
Expand Down
8 changes: 2 additions & 6 deletions adapt/src/main/java/io/github/vshnv/adapt/extensions/View.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@ package io.github.vshnv.adapt.extensions

import android.view.View
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.runtime.R
import androidx.lifecycle.ViewTreeLifecycleOwner

internal fun View.findViewTreeLifecycleOwner(): LifecycleOwner? {
return generateSequence(this) { currentView ->
currentView.parent as? View
}.mapNotNull { viewParent ->
viewParent.getTag(R.id.view_tree_lifecycle_owner) as? LifecycleOwner
}.firstOrNull()
return ViewTreeLifecycleOwner.get(this)
}
6 changes: 3 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.0.2" apply false
id("com.android.library") version "8.0.2" apply false
id("org.jetbrains.kotlin.android") version "1.8.20" apply false
id("com.android.application") version "8.7.3" apply false
id("com.android.library") version "8.7.3" apply false
id("org.jetbrains.kotlin.android") version "2.0.20" apply false
}

2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Mon Feb 26 18:14:07 IST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists