Skip to content
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

Closed
wants to merge 1 commit into from
Closed
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
5 changes: 5 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ android {
targetCompatibility rootProject.ext.javaTargetCompatibility
}

kotlinOptions {
jvmTarget = rootProject.ext.javaSourceCompatibility.toString()
}

lintOptions {
disable 'GoogleAppIndexingWarning'
abortOnError false
Expand All @@ -46,6 +50,7 @@ dependencies {

implementation project(':library')
implementation project(':paging')
implementation project(':paging3')
implementation project(':kotlin-dsl')
implementation project(':kotlin-dsl-layoutcontainer')
implementation project(':kotlin-dsl-viewbinding')
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<activity android:name=".animations.AnimationDiffUtilsActivity" />
<activity android:name=".DiffActivity" />
<activity android:name=".pagination.PaginationActivity"></activity>
<activity android:name=".paging3.Paging3Activity" />
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.hannesdorfmann.adapterdelegates4.sample.model.Gecko;
import com.hannesdorfmann.adapterdelegates4.sample.model.Snake;
import com.hannesdorfmann.adapterdelegates4.sample.pagination.PaginationActivity;
import com.hannesdorfmann.adapterdelegates4.sample.paging3.Paging3Activity;

import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -63,6 +64,13 @@ public void onClick(View v) {
startActivity(new Intent(MainActivity.this, PaginationActivity.class));
}
});

findViewById(R.id.paging3).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(MainActivity.this, Paging3Activity.class));
}
});
}

private List<DisplayableItem> getAnimals() {
Expand Down
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)
}
}
7 changes: 7 additions & 0 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
android:layout_weight="1"
android:text="Pagination" />

<Button
android:id="@+id/paging3"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Paging3" />

</LinearLayout>

</LinearLayout>
5 changes: 3 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ ext {
androidAnnotations = 'androidx.annotation:annotation:1.0.0'
core = 'androidx.core:core:1.0.0'
paging = "androidx.paging:paging-runtime:2.0.0"
paging3 = "androidx.paging:paging-runtime:3.0.0-alpha02"
compileSdk = 28

javaSourceCompatibility = JavaVersion.VERSION_1_7
javaTargetCompatibility = JavaVersion.VERSION_1_7
javaSourceCompatibility = JavaVersion.VERSION_1_8
javaTargetCompatibility = JavaVersion.VERSION_1_8
}
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

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

)

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
Copy link

@PavelSidyakin PavelSidyakin Sep 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

item as Any
Sometimes it causes a crash that null cannot be cast to non-null type.
Consider adding the non-null check:
if (item != null) { holder._item = item holder._bind?.invoke(payloads) }

Choose a reason for hiding this comment

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

Loading