From 2a4637c34f283a1699964a44f5a63df464dbd2bc Mon Sep 17 00:00:00 2001 From: Aleksey Smovzhenko Date: Wed, 29 Jan 2020 14:47:10 +0200 Subject: [PATCH] added first version of paging --- build.gradle | 2 + library/build.gradle | 9 +- .../auxility/baseadapter/EndlessAdapter.java | 147 ++++++++++++++++++ .../sample/EndlessAdapterSampleFragment.kt | 13 ++ .../sample/EndlessAdapterViewModel.kt | 13 ++ .../baseadapter/sample/EndlessRVItem.kt | 40 +++++ .../baseadapter/sample/MainFragment.kt | 51 +++--- .../baseadapter/sample/ProgressItem.kt | 7 + sample/src/main/res/layout/fragment_main.xml | 18 ++- .../src/main/res/layout/item_endless_rv.xml | 21 +++ sample/src/main/res/layout/item_progress.xml | 24 +++ sample/src/main/res/navigation/nav_graph.xml | 8 + sample/src/main/res/values/strings.xml | 1 + 13 files changed, 327 insertions(+), 27 deletions(-) create mode 100644 library/src/main/java/dev/auxility/baseadapter/EndlessAdapter.java create mode 100644 sample/src/main/java/dev/auxility/baseadapter/sample/EndlessAdapterSampleFragment.kt create mode 100644 sample/src/main/java/dev/auxility/baseadapter/sample/EndlessAdapterViewModel.kt create mode 100644 sample/src/main/java/dev/auxility/baseadapter/sample/EndlessRVItem.kt create mode 100644 sample/src/main/java/dev/auxility/baseadapter/sample/ProgressItem.kt create mode 100644 sample/src/main/res/layout/item_endless_rv.xml create mode 100644 sample/src/main/res/layout/item_progress.xml diff --git a/build.gradle b/build.gradle index c7af5b9..e4fc6e1 100644 --- a/build.gradle +++ b/build.gradle @@ -8,8 +8,10 @@ buildscript { minVersion = 14 gradlePluginVersion = '3.5.0' kotlinVersion = '1.3.31' + coroutine_version = '1.3.0-RC2' materialVersion = '1.0.0' versionerVersion = '0.4.1' + lifecycle_version = '2.2.0-alpha04' jUnitVersion = '4.12' jUnitTestExtVersion = '1.1.1' espressoVersion = '3.2.0' diff --git a/library/build.gradle b/library/build.gradle index 77768a7..3a35ad4 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -9,7 +9,7 @@ android { minSdkVersion minVersion targetSdkVersion targetVersion versionCode gitVersioner.versionCode - versionName "2.0.1" + versionName "2.1.0" println('VERSION_CODE: ' + gitVersioner.versionCode) testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -33,6 +33,13 @@ dependencies { implementation "androidx.recyclerview:recyclerview:$materialVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version" + + //lifecycle + implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" + testImplementation "junit:junit:$jUnitVersion" androidTestImplementation "androidx.test.ext:junit:$jUnitTestExtVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" diff --git a/library/src/main/java/dev/auxility/baseadapter/EndlessAdapter.java b/library/src/main/java/dev/auxility/baseadapter/EndlessAdapter.java new file mode 100644 index 0000000..209198a --- /dev/null +++ b/library/src/main/java/dev/auxility/baseadapter/EndlessAdapter.java @@ -0,0 +1,147 @@ +package dev.auxility.baseadapter; + +import androidx.annotation.NonNull; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import dev.auxility.baseadapter.item.Item; +import dev.auxility.baseadapter.misc.function.Predicate; + +public class EndlessAdapter extends AbstractAdapterDecorator { + + private final int threshold; + private final OnLoadMoreListener listener; + private boolean inProgress = false; + private boolean complete = false; + + public EndlessAdapter( + @NonNull AbstractAdapter decoratedAdapter, + int threshold, + OnLoadMoreListener listener + ) { + super(decoratedAdapter); + this.threshold = threshold; + this.listener = listener; + } + + public EndlessAdapter(int threshold, OnLoadMoreListener listener) { + this(new BaseAdapter(), threshold, listener); + } + + @Override + public int getSize() { + int size = getAdapter().getSize(); + if (size == 0) { + checkThreshold(0); + } + return size; + } + + @NonNull + @Override + public ListIterator listIterator(int index) { + return getAdapter().listIterator(index); + } + + @NonNull + @Override + public V get(int index) { + checkThreshold(index); + return getAdapter().get(index); + } + + @NonNull + @Override + public List items() { + return getAdapter().items(); + } + + @NonNull + @Override + public V remove(int index) { + return getAdapter().remove(index); + } + + @Override + public boolean removeIf(@NonNull Predicate predicate, boolean withDiffUtil) { + return getAdapter().removeIf(predicate, withDiffUtil); + } + + @Override + public List removeRange(int beginIndex, int endIndex) { + return getAdapter().removeRange(beginIndex, endIndex); + } + + @Override + public void clear(boolean withDiffUtil) { + getAdapter().clear(); + } + + @Override + public void add(int index, @NonNull V element) { + getAdapter().add(index, element); + } + + @Override + public boolean addAll(int index, @NonNull Collection c) { + return getAdapter().addAll(index, c); + } + + @NonNull + @Override + public V set(int index, @NonNull V element) { + return getAdapter().set(index, element); + } + + @Override + public void set(@NonNull Collection c, boolean withDiffUtil) { + getAdapter().set(c, withDiffUtil); + } + + @NonNull + @Override + public Iterator iterator() { + return getAdapter().iterator(); + } + + public int getThreshold() { + return threshold; + } + + public boolean isInProgress() { + return inProgress; + } + + public void setInProgress(boolean inProgress) { + this.inProgress = inProgress; + } + + public boolean isComplete() { + return complete; + } + + public void setComplete(boolean complete) { + this.complete = complete; + } + + @Override + public void refresh() { + getAdapter().refresh(); + } + + private void checkThreshold(int index) { + if (!isComplete() && !isInProgress() && index >= getAdapter().getSize() - threshold) { + listener.onLoadMore(getAdapter().getSize()); + } + } + + public interface OnLoadMoreListener extends Serializable { + + void onLoadMore(int currentSize); + + } +} diff --git a/sample/src/main/java/dev/auxility/baseadapter/sample/EndlessAdapterSampleFragment.kt b/sample/src/main/java/dev/auxility/baseadapter/sample/EndlessAdapterSampleFragment.kt new file mode 100644 index 0000000..c9cb5fe --- /dev/null +++ b/sample/src/main/java/dev/auxility/baseadapter/sample/EndlessAdapterSampleFragment.kt @@ -0,0 +1,13 @@ +package dev.auxility.baseadapter.sample + +import androidx.lifecycle.ViewModelProviders + +class EndlessAdapterSampleFragment : TabbedFragment() { + override val titleRes: Int = R.string.endless_adapter_sample + + override val viewModel: TabbedViewModel by lazy { + ViewModelProviders.of(this) + .get(EndlessAdapterViewModel::class.java) + } + +} \ No newline at end of file diff --git a/sample/src/main/java/dev/auxility/baseadapter/sample/EndlessAdapterViewModel.kt b/sample/src/main/java/dev/auxility/baseadapter/sample/EndlessAdapterViewModel.kt new file mode 100644 index 0000000..8f15d77 --- /dev/null +++ b/sample/src/main/java/dev/auxility/baseadapter/sample/EndlessAdapterViewModel.kt @@ -0,0 +1,13 @@ +package dev.auxility.baseadapter.sample + +import androidx.lifecycle.viewModelScope +import dev.auxility.baseadapter.Adapter +import dev.auxility.baseadapter.BaseAdapter +import dev.auxility.baseadapter.item.TitledItem + +class EndlessAdapterViewModel : TabbedViewModel() { + + override val adapter: Adapter = BaseAdapter( + listOf(EndlessRVItem(viewModelScope)) + ) +} \ No newline at end of file diff --git a/sample/src/main/java/dev/auxility/baseadapter/sample/EndlessRVItem.kt b/sample/src/main/java/dev/auxility/baseadapter/sample/EndlessRVItem.kt new file mode 100644 index 0000000..8dc54b5 --- /dev/null +++ b/sample/src/main/java/dev/auxility/baseadapter/sample/EndlessRVItem.kt @@ -0,0 +1,40 @@ +package dev.auxility.baseadapter.sample + +import android.util.Log +import dev.auxility.baseadapter.EndlessAdapter +import dev.auxility.baseadapter.item.Item +import dev.auxility.baseadapter.item.TitledItem +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +const val THRESHOLD = 10 +const val PAGE_SIZE = 20 +const val TOTAL_COUNT = 100 + +class EndlessRVItem(private val scope: CoroutineScope) : TitledItem, EndlessAdapter.OnLoadMoreListener { + + val adapter: EndlessAdapter = EndlessAdapter(THRESHOLD, this) + + override fun onLoadMore(currentSize: Int) { + Log.d("mytag", "loading $PAGE_SIZE items starting at $currentSize from $TOTAL_COUNT") + if (currentSize < TOTAL_COUNT) { + adapter.isInProgress = true + scope.launch { + adapter.add(ProgressItem()) + delay(2000) + adapter.remove(adapter.size - 1) + adapter.addAll(List(PAGE_SIZE) { + TestItem(currentSize + it) + }) + adapter.isInProgress = false + } + } else { + adapter.isComplete = true + } + } + + override fun getLayoutId(): Int = R.layout.item_endless_rv + + override fun getTitle(): String = "RecyclerView" +} \ No newline at end of file diff --git a/sample/src/main/java/dev/auxility/baseadapter/sample/MainFragment.kt b/sample/src/main/java/dev/auxility/baseadapter/sample/MainFragment.kt index 85a42b9..235fb10 100644 --- a/sample/src/main/java/dev/auxility/baseadapter/sample/MainFragment.kt +++ b/sample/src/main/java/dev/auxility/baseadapter/sample/MainFragment.kt @@ -11,29 +11,34 @@ import dev.auxility.baseadapter.sample.databinding.FragmentMainBinding class MainFragment : androidx.fragment.app.Fragment() { - private lateinit var binding: FragmentMainBinding + private lateinit var binding: FragmentMainBinding - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - binding = DataBindingUtil.inflate( - inflater, - layout.fragment_main, container, false - ) - binding.baseAdapterBtn.setOnClickListener { btn -> - btn.findNavController() - .navigate( - R.id.action_mainFragment_to_baseAdapterSampleFragment - ) + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = DataBindingUtil.inflate( + inflater, + layout.fragment_main, container, false + ) + binding.baseAdapterBtn.setOnClickListener { btn -> + btn.findNavController() + .navigate( + R.id.action_mainFragment_to_baseAdapterSampleFragment + ) + } + binding.filterableAdapterBtn.setOnClickListener { btn -> + btn.findNavController() + .navigate( + R.id.action_mainFragment_to_filterableAdapterSampleFragment + ) + } + binding.endlessAdapterBtn.setOnClickListener { + it.findNavController().navigate( + R.id.action_mainFragment_to_endlessAdapterSampleFragment + ) + } + return binding.root } - binding.filterableAdapterBtn.setOnClickListener { btn -> - btn.findNavController() - .navigate( - R.id.action_mainFragment_to_filterableAdapterSampleFragment - ) - } - return binding.root - } } \ No newline at end of file diff --git a/sample/src/main/java/dev/auxility/baseadapter/sample/ProgressItem.kt b/sample/src/main/java/dev/auxility/baseadapter/sample/ProgressItem.kt new file mode 100644 index 0000000..48aa689 --- /dev/null +++ b/sample/src/main/java/dev/auxility/baseadapter/sample/ProgressItem.kt @@ -0,0 +1,7 @@ +package dev.auxility.baseadapter.sample + +import dev.auxility.baseadapter.item.Item + +class ProgressItem : Item { + override fun getLayoutId(): Int = R.layout.item_progress +} \ No newline at end of file diff --git a/sample/src/main/res/layout/fragment_main.xml b/sample/src/main/res/layout/fragment_main.xml index 538df6f..54f8105 100644 --- a/sample/src/main/res/layout/fragment_main.xml +++ b/sample/src/main/res/layout/fragment_main.xml @@ -47,13 +47,25 @@ android:layout_height="wrap_content" android:layout_marginTop="24dp" android:text="@string/filterableadapter_sample" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/endlessAdapterBtn" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/baseAdapterBtn" - app:layout_constraintWidth_percent="0.7" - /> + app:layout_constraintWidth_percent="0.7" /> + +