-
Notifications
You must be signed in to change notification settings - Fork 314
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#88 Jetpack Paging 3 support #91
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package com.hannesdorfmann.adapterdelegates4.sample.paging3 | ||
|
||
import android.content.Context | ||
import android.os.Bundle | ||
import android.view.LayoutInflater | ||
import android.view.View | ||
import android.view.ViewGroup | ||
import android.widget.TextView | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.lifecycle.lifecycleScope | ||
import androidx.paging.Pager | ||
import androidx.paging.PagingConfig | ||
import androidx.paging.cachedIn | ||
import androidx.recyclerview.widget.DiffUtil | ||
import androidx.recyclerview.widget.LinearLayoutManager | ||
import androidx.recyclerview.widget.RecyclerView | ||
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate | ||
import com.hannesdorfmann.adapterdelegates4.AdapterItemProvider | ||
import com.hannesdorfmann.adapterdelegates4.paging3.PagingDelegationAdapter | ||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterLayoutContainer | ||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterViewBinding | ||
import com.hannesdorfmann.adapterdelegates4.sample.R | ||
import com.hannesdorfmann.adapterdelegates4.sample.databinding.ItemDogBinding | ||
import com.hannesdorfmann.adapterdelegates4.sample.model.Cat | ||
import com.hannesdorfmann.adapterdelegates4.sample.model.DisplayableItem | ||
import com.hannesdorfmann.adapterdelegates4.sample.model.Dog | ||
import com.hannesdorfmann.adapterdelegates4.sample.model.Snake | ||
import kotlinx.android.synthetic.main.item_snake.* | ||
import kotlinx.coroutines.flow.collectLatest | ||
import kotlinx.coroutines.launch | ||
|
||
class Paging3Activity : AppCompatActivity() { | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContentView(R.layout.activity_pagination) | ||
|
||
val recyclerView: RecyclerView = findViewById(R.id.recyclerView) | ||
recyclerView.layoutManager = LinearLayoutManager(this) | ||
|
||
val adapter = PagingDelegationAdapter<DisplayableItem>( | ||
callback, | ||
CatAdapterDelegate(this), | ||
adapterViewBinding<Dog, DisplayableItem, ItemDogBinding>( | ||
viewBinding = { layoutInflater, parent -> | ||
ItemDogBinding.inflate(layoutInflater, parent, false) | ||
} | ||
) { | ||
bind { binding.name.text = item.name } | ||
}, | ||
adapterLayoutContainer<Snake, DisplayableItem>(R.layout.item_snake) { | ||
bind { | ||
name.text = item.name | ||
race.text = item.race | ||
} | ||
} | ||
) | ||
|
||
recyclerView.adapter = adapter | ||
|
||
lifecycleScope.launch { | ||
Pager<Int, DisplayableItem>( | ||
PagingConfig(pageSize = 10, enablePlaceholders = false) | ||
) { | ||
SampleDatasource() | ||
}.flow | ||
.cachedIn(lifecycleScope) | ||
.collectLatest { adapter.submitData(it) } | ||
} | ||
} | ||
|
||
private val callback = object : DiffUtil.ItemCallback<DisplayableItem>() { | ||
override fun areItemsTheSame(oldItem: DisplayableItem, newItem: DisplayableItem): Boolean { | ||
if (oldItem === newItem || oldItem == newItem) { | ||
return true | ||
} | ||
return oldItem.javaClass == newItem.javaClass | ||
} | ||
|
||
override fun areContentsTheSame(oldItem: DisplayableItem, newItem: DisplayableItem): Boolean { | ||
// TODO: implement that properly | ||
return false | ||
} | ||
|
||
} | ||
} | ||
|
||
private class CatAdapterDelegate( | ||
context: Context | ||
) : AdapterDelegate<AdapterItemProvider<DisplayableItem>>() { | ||
|
||
private val layoutInflater = LayoutInflater.from(context) | ||
|
||
override fun isForViewType(items: AdapterItemProvider<DisplayableItem>, position: Int): Boolean { | ||
return items.getAdapterItem(position) is Cat | ||
} | ||
|
||
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder { | ||
return CatViewHolder(layoutInflater.inflate(R.layout.item_cat, parent, false)) | ||
} | ||
|
||
override fun onBindViewHolder(items: AdapterItemProvider<DisplayableItem>, position: Int, holder: RecyclerView.ViewHolder, payloads: MutableList<Any>) { | ||
val vh = holder as CatViewHolder | ||
val cat = items.getAdapterItem(position) as Cat | ||
|
||
vh.name.text = cat.name | ||
} | ||
|
||
class CatViewHolder( | ||
itemView: View | ||
) : RecyclerView.ViewHolder(itemView) { | ||
|
||
val name: TextView = itemView.findViewById(R.id.name) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package com.hannesdorfmann.adapterdelegates4.sample.paging3 | ||
|
||
import androidx.paging.PagingSource | ||
import com.hannesdorfmann.adapterdelegates4.sample.model.Cat | ||
import com.hannesdorfmann.adapterdelegates4.sample.model.DisplayableItem | ||
import com.hannesdorfmann.adapterdelegates4.sample.model.Dog | ||
import com.hannesdorfmann.adapterdelegates4.sample.model.Snake | ||
import java.lang.IllegalStateException | ||
import java.util.* | ||
|
||
class SampleDatasource : PagingSource<Int, DisplayableItem>() { | ||
|
||
private val random = Random() | ||
|
||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DisplayableItem> { | ||
val result = mutableListOf<DisplayableItem>() | ||
for (i in 0 until params.loadSize) { | ||
when (val itemType = random.nextInt(3)) { | ||
0 -> result.add(Cat("Cat $i")) | ||
1 -> result.add(Dog("Dog $i")) | ||
2 -> result.add(Snake("Snake $i", "Race")) | ||
else -> throw IllegalStateException("Random returned $itemType") | ||
} | ||
} | ||
|
||
return LoadResult.Page(data = result, prevKey = params.key, nextKey = (params.key ?: 0) + 1) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package com.hannesdorfmann.adapterdelegates4.dsl | ||
|
||
import android.view.LayoutInflater | ||
import android.view.View | ||
import android.view.ViewGroup | ||
import androidx.annotation.LayoutRes | ||
import androidx.recyclerview.widget.RecyclerView | ||
import com.hannesdorfmann.adapterdelegates4.AbsItemAdapterDelegate | ||
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate | ||
import com.hannesdorfmann.adapterdelegates4.AdapterItemProvider | ||
|
||
inline fun <reified I : T, T> adapterLayoutContainer( | ||
@LayoutRes layout: Int, | ||
noinline on: (item: T, items: AdapterItemProvider<T>, position: Int) -> Boolean = { item, _, _ -> item is I }, | ||
noinline layoutInflater: (parent: ViewGroup, layoutRes: Int) -> View = { parent, layoutRes -> | ||
LayoutInflater.from(parent.context).inflate( | ||
layoutRes, | ||
parent, | ||
false | ||
) | ||
}, | ||
noinline block: AdapterDelegateLayoutContainerViewHolder<I>.() -> Unit | ||
): AdapterDelegate<AdapterItemProvider<T>> { | ||
|
||
return DslLayoutContainerAdapterDelegate( | ||
layout = layout, | ||
on = on, | ||
initializerBlock = block, | ||
layoutInflater = layoutInflater | ||
) | ||
} | ||
|
||
@PublishedApi | ||
internal class DslLayoutContainerAdapterDelegate<I : T, T>( | ||
@LayoutRes private val layout: Int, | ||
private val on: (item: T, items: AdapterItemProvider<T>, position: Int) -> Boolean, | ||
private val initializerBlock: AdapterDelegateLayoutContainerViewHolder<I>.() -> Unit, | ||
private val layoutInflater: (parent: ViewGroup, layoutRes: Int) -> View | ||
) : AbsItemAdapterDelegate<I, T, AdapterDelegateLayoutContainerViewHolder<I>>() { | ||
|
||
override fun isForViewType(item: T, items: AdapterItemProvider<T>, position: Int): Boolean = on( | ||
item, items, position | ||
) | ||
|
||
override fun onCreateViewHolder(parent: ViewGroup): AdapterDelegateLayoutContainerViewHolder<I> = | ||
AdapterDelegateLayoutContainerViewHolder<I>( | ||
layoutInflater(parent, layout) | ||
).also { | ||
initializerBlock(it) | ||
} | ||
|
||
override fun onBindViewHolder( | ||
item: I, | ||
holder: AdapterDelegateLayoutContainerViewHolder<I>, | ||
payloads: List<Any> | ||
) { | ||
holder._item = item as Any | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When placeholders are using, item can be null. So maybe it's better to make _item nullable. |
||
holder._bind?.invoke(payloads) // It's ok to have an AdapterDelegate without binding block (i.e. static content) | ||
} | ||
|
||
override fun onViewRecycled(holder: RecyclerView.ViewHolder) { | ||
@Suppress("UNCHECKED_CAST") | ||
val vh = (holder as AdapterDelegateLayoutContainerViewHolder<I>) | ||
|
||
vh._onViewRecycled?.invoke() | ||
} | ||
|
||
override fun onFailedToRecycleView(holder: RecyclerView.ViewHolder): Boolean { | ||
@Suppress("UNCHECKED_CAST") | ||
val vh = (holder as AdapterDelegateLayoutContainerViewHolder<I>) | ||
val block = vh._onFailedToRecycleView | ||
return if (block == null) { | ||
super.onFailedToRecycleView(holder) | ||
} else { | ||
block() | ||
} | ||
} | ||
|
||
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) { | ||
@Suppress("UNCHECKED_CAST") | ||
val vh = (holder as AdapterDelegateLayoutContainerViewHolder<I>) | ||
vh._onViewAttachedToWindow?.invoke() | ||
} | ||
|
||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) { | ||
@Suppress("UNCHECKED_CAST") | ||
val vh = (holder as AdapterDelegateLayoutContainerViewHolder<I>) | ||
vh._onViewDetachedFromWindow?.invoke() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package com.hannesdorfmann.adapterdelegates4.dsl | ||
|
||
import android.view.LayoutInflater | ||
import android.view.ViewGroup | ||
import androidx.viewbinding.ViewBinding | ||
import com.hannesdorfmann.adapterdelegates4.AbsItemAdapterDelegate | ||
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate | ||
import com.hannesdorfmann.adapterdelegates4.AdapterItemProvider | ||
|
||
inline fun <reified I : T, T, V : ViewBinding> adapterViewBinding( | ||
noinline viewBinding: (layoutInflater: LayoutInflater, parent: ViewGroup) -> V, | ||
noinline on: (item: T, items: AdapterItemProvider<T>, position: Int) -> Boolean = { item, _, _ -> item is I}, | ||
noinline layoutInflater: (parent: ViewGroup) -> LayoutInflater = { parent -> LayoutInflater.from(parent.context)}, | ||
noinline block: AdapterDelegateViewBindingViewHolder<I, V>.() -> Unit | ||
): AdapterDelegate<AdapterItemProvider<T>> { | ||
return DslViewBindingAdapterDelegate( | ||
binding = viewBinding, | ||
on = on, | ||
initializerBlock = block, | ||
layoutInflater = layoutInflater | ||
) | ||
} | ||
|
||
@PublishedApi | ||
internal class DslViewBindingAdapterDelegate<I : T, T, V : ViewBinding>( | ||
private val binding: (layoutInflater: LayoutInflater, parent: ViewGroup) -> V, | ||
private val on: (item: T, items: AdapterItemProvider<T>, position: Int) -> Boolean, | ||
private val initializerBlock: (AdapterDelegateViewBindingViewHolder<I, V>) -> Unit, | ||
private val layoutInflater: (parent: ViewGroup) -> LayoutInflater | ||
) : AbsItemAdapterDelegate<I, T, AdapterDelegateViewBindingViewHolder<I, V>>() { | ||
|
||
override fun isForViewType(item: T, items: AdapterItemProvider<T>, position: Int): Boolean = on( | ||
item, items, position | ||
) | ||
|
||
override fun onCreateViewHolder(parent: ViewGroup): AdapterDelegateViewBindingViewHolder<I, V> { | ||
val binding = binding(layoutInflater(parent), parent) | ||
return AdapterDelegateViewBindingViewHolder<I, V>( | ||
binding | ||
).also { | ||
initializerBlock(it) | ||
} | ||
} | ||
|
||
override fun onBindViewHolder(item: I, holder: AdapterDelegateViewBindingViewHolder<I, V>, payloads: List<Any>) { | ||
holder._item = item as Any | ||
holder._bind?.invoke(payloads) // It's ok to have an AdapterDelegate without binding block (i.e. static content) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.hannesdorfmann.adapterdelegates4; | ||
|
||
import android.view.ViewGroup; | ||
|
||
import androidx.annotation.NonNull; | ||
import androidx.recyclerview.widget.RecyclerView; | ||
|
||
import java.util.List; | ||
|
||
public abstract class AbsItemAdapterDelegate<I extends T, T, VH extends RecyclerView.ViewHolder> | ||
extends AdapterDelegate<AdapterItemProvider<T>> { | ||
|
||
@Override | ||
protected final boolean isForViewType(@NonNull AdapterItemProvider<T> items, int position) { | ||
return isForViewType(items.getAdapterItem(position), items, position); | ||
} | ||
|
||
@Override | ||
protected final void onBindViewHolder(@NonNull AdapterItemProvider<T> items, int position, | ||
@NonNull RecyclerView.ViewHolder holder, @NonNull List<Object> payloads) { | ||
onBindViewHolder((I) items.getAdapterItem(position), (VH) holder, payloads); | ||
} | ||
|
||
protected abstract boolean isForViewType(@NonNull T item, @NonNull AdapterItemProvider<T> items, int position); | ||
|
||
@NonNull | ||
@Override | ||
protected abstract VH onCreateViewHolder(@NonNull ViewGroup parent); | ||
|
||
protected abstract void onBindViewHolder(@NonNull I item, @NonNull VH holder, | ||
@NonNull List<Object> payloads); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here should be !=null check.
override fun isForViewType(item: T, items: AdapterItemProvider<T>, position: Int): Boolean { return item != null && on(item, items, position) }