Skip to content

Commit 1454d32

Browse files
add endless scrolling
1 parent 27994bb commit 1454d32

10 files changed

Lines changed: 309 additions & 17 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.smarttoolfactory.home.adapter
2+
3+
class FooterAdapter

features/home/src/main/java/com/smarttoolfactory/home/adapter/HomeViewPager2FragmentStateAdapter.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,22 @@ class HomeViewPager2FragmentStateAdapter(fragmentManager: FragmentManager, lifec
2323
override fun createFragment(position: Int): Fragment {
2424

2525
return when (position) {
26-
27-
// Fragment with Flow
2826
0 -> NavHostContainerFragment.createNavHostContainerFragment(
2927
R.layout.fragment_navhost_property_list_flow,
3028
R.id.nested_nav_host_fragment_property_list
3129
)
3230

33-
// Fragment with Pagination
34-
else -> NavHostContainerFragment.createNavHostContainerFragment(
31+
// Fragment with RxJava3
32+
1 -> NavHostContainerFragment.createNavHostContainerFragment(
3533
R.layout.fragment_navhost_property_list_rxjava3,
3634
R.id.nested_nav_host_fragment_property_list
3735
)
36+
37+
// Fragment with Flow + Pagination
38+
else -> NavHostContainerFragment.createNavHostContainerFragment(
39+
R.layout.fragment_navhost_property_list_paged,
40+
R.id.nested_nav_host_fragment_property_list
41+
)
3842
}
3943
}
4044
}

features/home/src/main/java/com/smarttoolfactory/home/adapter/PropertyListAdapter.kt

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,11 @@ class PropertyItemListAdapter(
7878
*/
7979
class PropertyItemDiffCallback : DiffUtil.ItemCallback<PropertyItem>() {
8080

81-
override fun areItemsTheSame(
82-
oldItem: PropertyItem,
83-
newItem: PropertyItem
84-
): Boolean {
85-
return oldItem === newItem
81+
override fun areItemsTheSame(oldItem: PropertyItem, newItem: PropertyItem): Boolean {
82+
return oldItem.id == newItem.id
8683
}
8784

88-
override fun areContentsTheSame(
89-
oldItem: PropertyItem,
90-
newItem: PropertyItem
91-
): Boolean {
92-
return oldItem.id == newItem.id
85+
override fun areContentsTheSame(oldItem: PropertyItem, newItem: PropertyItem): Boolean {
86+
return oldItem == newItem
9387
}
9488
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package com.smarttoolfactory.home.propertylist
2+
3+
import android.os.Bundle
4+
import android.widget.Toast
5+
import androidx.core.os.bundleOf
6+
import androidx.fragment.app.activityViewModels
7+
import androidx.recyclerview.widget.LinearLayoutManager
8+
import com.smarttoolfactory.core.di.CoreModuleDependencies
9+
import com.smarttoolfactory.core.ui.fragment.DynamicNavigationFragment
10+
import com.smarttoolfactory.core.util.EndlessScrollListener
11+
import com.smarttoolfactory.core.util.observe
12+
import com.smarttoolfactory.home.R
13+
import com.smarttoolfactory.home.adapter.PropertyItemListAdapter
14+
import com.smarttoolfactory.home.databinding.FragmentPropertyListPagedBinding
15+
import com.smarttoolfactory.home.di.DaggerHomeComponent
16+
import com.smarttoolfactory.home.viewmodel.HomeToolbarVM
17+
import com.smarttoolfactory.home.viewmodel.PagedPropertyListViewModel
18+
import dagger.hilt.android.EntryPointAccessors
19+
import javax.inject.Inject
20+
21+
class PagedPropertyListFragment :
22+
DynamicNavigationFragment<FragmentPropertyListPagedBinding>(),
23+
EndlessScrollListener.ScrollToBottomListener {
24+
25+
@Inject
26+
lateinit var viewModel: PagedPropertyListViewModel
27+
28+
lateinit var itemListAdapter: PropertyItemListAdapter
29+
30+
/**
31+
* Listener for listening scrolling to last item of RecyclerView
32+
*/
33+
private lateinit var endlessScrollListener: EndlessScrollListener
34+
35+
/**
36+
* ViewModel for setting sort filter on top menu and property list fragments
37+
*/
38+
private val toolbarVM by activityViewModels<HomeToolbarVM>()
39+
40+
override fun getLayoutRes(): Int = R.layout.fragment_property_list_paged
41+
42+
override fun onCreate(savedInstanceState: Bundle?) {
43+
initCoreDependentInjection()
44+
super.onCreate(savedInstanceState)
45+
viewModel.refreshPropertyList()
46+
}
47+
48+
override fun bindViews() {
49+
dataBinding.viewModel = viewModel
50+
51+
dataBinding.recyclerView.apply {
52+
53+
// Set Layout manager
54+
val linearLayoutManager =
55+
LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
56+
57+
this.layoutManager = linearLayoutManager
58+
59+
// Set RecyclerViewAdapter
60+
itemListAdapter = PropertyItemListAdapter(
61+
R.layout.row_property,
62+
viewModel::onClick,
63+
viewModel::onLikeButtonClick
64+
65+
)
66+
67+
// Set Adapter
68+
this.adapter = itemListAdapter
69+
70+
// Set RecyclerView layout manager, adapter, and scroll listener for infinite scrolling
71+
endlessScrollListener =
72+
EndlessScrollListener(linearLayoutManager, this@PagedPropertyListFragment)
73+
this.addOnScrollListener(endlessScrollListener)
74+
}
75+
76+
val swipeRefreshLayout = dataBinding.swipeRefreshLayout
77+
78+
swipeRefreshLayout.setOnRefreshListener {
79+
swipeRefreshLayout.isRefreshing = false
80+
viewModel.refreshPropertyList()
81+
}
82+
83+
subscribeViewModelSortChange()
84+
85+
subscribeGoToDetailScreen()
86+
}
87+
88+
/**
89+
* When sort key is fetched from database change the one belong to Toolbar
90+
*/
91+
private fun subscribeViewModelSortChange() {
92+
viewLifecycleOwner.observe(viewModel.orderKey) {
93+
toolbarVM.currentSortFilter = it
94+
}
95+
}
96+
97+
private fun subscribeToolbarSortChange() {
98+
99+
viewLifecycleOwner.observe(toolbarVM.queryBySort) {
100+
it.getContentIfNotHandled()?.let { orderBy ->
101+
viewModel.refreshPropertyList(orderBy)
102+
toolbarVM.currentSortFilter = orderBy
103+
}
104+
}
105+
}
106+
107+
private fun subscribeGoToDetailScreen() {
108+
109+
viewModel.goToDetailScreen.observe(
110+
viewLifecycleOwner,
111+
{
112+
113+
it.getContentIfNotHandled()?.let { propertyItem ->
114+
val bundle = bundleOf("property" to propertyItem)
115+
}
116+
}
117+
)
118+
}
119+
120+
private fun initCoreDependentInjection() {
121+
122+
val coreModuleDependencies = EntryPointAccessors.fromApplication(
123+
requireActivity().applicationContext,
124+
CoreModuleDependencies::class.java
125+
)
126+
127+
DaggerHomeComponent.factory().create(
128+
dependentModule = coreModuleDependencies,
129+
fragment = this
130+
)
131+
.inject(this)
132+
}
133+
134+
override fun onResume() {
135+
super.onResume()
136+
subscribeToolbarSortChange()
137+
}
138+
139+
override fun onPause() {
140+
super.onPause()
141+
toolbarVM.queryBySort.removeObservers(viewLifecycleOwner)
142+
}
143+
144+
override fun onScrollToBottom() {
145+
viewModel.getPropertyList()
146+
Toast.makeText(requireContext(), "BOTTOM SCROLL", Toast.LENGTH_SHORT).show()
147+
}
148+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<layout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto">
4+
5+
<androidx.constraintlayout.widget.ConstraintLayout
6+
android:layout_width="match_parent"
7+
android:layout_height="match_parent">
8+
9+
<androidx.fragment.app.FragmentContainerView
10+
android:id="@+id/nested_nav_host_fragment_property_list"
11+
android:name="androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment"
12+
android:layout_width="0dp"
13+
android:layout_height="0dp"
14+
app:defaultNavHost="true"
15+
app:layout_constraintBottom_toBottomOf="parent"
16+
app:layout_constraintLeft_toLeftOf="parent"
17+
app:layout_constraintRight_toRightOf="parent"
18+
app:layout_constraintTop_toTopOf="parent"
19+
app:navGraph="@navigation/nav_graph_property_list_paged" />
20+
21+
</androidx.constraintlayout.widget.ConstraintLayout>
22+
23+
</layout>

features/home/src/main/res/layout/fragment_property_list.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
android:layout_width="match_parent"
2828
android:layout_height="match_parent"
2929
android:layout_marginTop="18dp"
30+
android:scrollbars="vertical"
3031
app:items="@{viewModel.propertyListViewState.data}"
3132
app:layout_constraintBottom_toBottomOf="parent"
3233
app:layout_constraintEnd_toEndOf="parent"
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<layout xmlns:android="http://schemas.android.com/apk/res/android"
4+
xmlns:app="http://schemas.android.com/apk/res-auto">
5+
6+
<data>
7+
8+
<import type="android.view.View" />
9+
10+
<variable
11+
name="viewModel"
12+
type="com.smarttoolfactory.home.viewmodel.PagedPropertyListViewModel" />
13+
</data>
14+
15+
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
16+
android:id="@+id/swipeRefreshLayout"
17+
android:layout_width="match_parent"
18+
android:layout_height="match_parent">
19+
20+
<androidx.constraintlayout.widget.ConstraintLayout
21+
android:layout_width="match_parent"
22+
android:layout_height="match_parent">
23+
24+
<androidx.recyclerview.widget.RecyclerView
25+
android:id="@+id/recycler_view"
26+
android:layout_width="match_parent"
27+
android:layout_height="0dp"
28+
android:layout_marginTop="18dp"
29+
app:items="@{viewModel.propertyListViewState.data}"
30+
app:layout_constraintBottom_toTopOf="@+id/progress_bar"
31+
app:layout_constraintEnd_toEndOf="parent"
32+
app:layout_constraintStart_toStartOf="parent"
33+
android:scrollbars="vertical"
34+
app:layout_constraintTop_toTopOf="parent" />
35+
36+
<androidx.appcompat.widget.AppCompatTextView
37+
android:id="@+id/tv_error"
38+
visibilityBasedOn="@{viewModel.propertyListViewState.shouldShowErrorMessage()}"
39+
android:layout_width="wrap_content"
40+
android:layout_height="wrap_content"
41+
android:text="@{`⚠️ ` + viewModel.propertyListViewState.errorMessage}"
42+
app:layout_constraintBottom_toBottomOf="parent"
43+
app:layout_constraintEnd_toEndOf="parent"
44+
app:layout_constraintStart_toStartOf="parent"
45+
app:layout_constraintTop_toTopOf="@+id/recycler_view" />
46+
47+
<ProgressBar
48+
android:id="@+id/progress_bar"
49+
visibilityBasedOn="@{viewModel.propertyListViewState.loading}"
50+
android:layout_width="wrap_content"
51+
android:layout_height="0dp"
52+
app:layout_constraintHeight_percent=".2"
53+
app:layout_constraintBottom_toBottomOf="parent"
54+
app:layout_constraintEnd_toEndOf="parent"
55+
app:layout_constraintStart_toStartOf="parent" />
56+
</androidx.constraintlayout.widget.ConstraintLayout>
57+
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
58+
59+
</layout>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:id="@+id/nav_graph_property_list"
6+
app:startDestination="@id/propertyListFragment">
7+
8+
<fragment
9+
android:id="@+id/propertyListFragment"
10+
android:name="com.smarttoolfactory.home.propertylist.PagedPropertyListFragment"
11+
android:label="Flow + Page"
12+
tools:layout="@layout/fragment_property_list_paged" />
13+
14+
</navigation>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.smarttoolfactory.core.util
2+
3+
import androidx.recyclerview.widget.LinearLayoutManager
4+
import androidx.recyclerview.widget.RecyclerView
5+
6+
class EndlessScrollListener(
7+
private val linearLayoutManager: LinearLayoutManager,
8+
private val listener: ScrollToBottomListener
9+
) : RecyclerView.OnScrollListener() {
10+
11+
private var previousTotal = 0
12+
private var loading = true
13+
private val visibleThreshold = 8
14+
private var firstVisibleItem = 0
15+
private var visibleItemCount = 0
16+
private var totalItemCount = 0
17+
18+
fun onRefresh() {
19+
previousTotal = 0
20+
}
21+
22+
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
23+
super.onScrolled(recyclerView, dx, dy)
24+
25+
visibleItemCount = recyclerView.childCount
26+
totalItemCount = linearLayoutManager.itemCount
27+
firstVisibleItem = linearLayoutManager.findFirstVisibleItemPosition()
28+
29+
if (loading) {
30+
if (totalItemCount > previousTotal) {
31+
loading = false
32+
previousTotal = totalItemCount
33+
}
34+
}
35+
if (!loading && totalItemCount - visibleItemCount
36+
<= firstVisibleItem + visibleThreshold
37+
) {
38+
listener.onScrollToBottom()
39+
loading = true
40+
}
41+
}
42+
43+
interface ScrollToBottomListener {
44+
fun onScrollToBottom()
45+
}
46+
}

libraries/domain/src/main/java/com/smarttoolfactory/domain/usecase/GetPropertiesUseCasePaged.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class GetPropertiesUseCasePaged @Inject constructor(
4040
} else {
4141
repository.run {
4242

43-
deletePropertyEntities()
43+
// deletePropertyEntities()
4444

4545
val propertyCount = repository.getPropertyCount()
4646

@@ -49,8 +49,8 @@ class GetPropertiesUseCasePaged @Inject constructor(
4949
propertyEntity.insertOrder = propertyCount + index
5050
}
5151
savePropertyEntities(it)
52-
53-
getPropertyEntitiesFromLocal()
52+
val data = getPropertyEntitiesFromLocal()
53+
data
5454
}
5555
}
5656
}

0 commit comments

Comments
 (0)