Skip to content

Commit e334423

Browse files
committed
Add refresh
1 parent e73f650 commit e334423

File tree

5 files changed

+121
-26
lines changed

5 files changed

+121
-26
lines changed

app/src/main/java/com/hoc/pagination_mvi/ui/main/MainContract.kt

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,20 @@ import com.hoc.pagination_mvi.domain.entity.Post as PostDomain
1010
interface MainContract {
1111
data class ViewState(
1212
val items: List<Item>,
13-
val photoItems: List<Item.Photo>
13+
val photoItems: List<Item.Photo>,
14+
val isRefreshing: Boolean
1415
) {
1516

17+
val enableRefresh: Boolean
18+
get() {
19+
val horizontalList =
20+
items.singleOrNull { it is Item.HorizontalList } as? Item.HorizontalList ?: return false
21+
return !horizontalList.isLoading &&
22+
horizontalList.error === null &&
23+
(items.singleOrNull { it is Item.Placeholder } as? Item.Placeholder)
24+
?.state == PlaceholderState.Idle
25+
}
26+
1627
fun canLoadNextPage(): Boolean {
1728
return photoItems.isNotEmpty() &&
1829
(items.singleOrNull { it is Item.Placeholder } as? Item.Placeholder)
@@ -67,7 +78,8 @@ interface MainContract {
6778
postItems = emptyList()
6879
)
6980
),
70-
photoItems = emptyList()
81+
photoItems = emptyList(),
82+
isRefreshing = false
7183
)
7284
}
7385
}
@@ -348,6 +360,25 @@ interface MainContract {
348360
}
349361
}
350362
}
363+
364+
sealed class Refresh : PartialStateChange() {
365+
data class Success(val photos: List<PhotoVS>, val posts: List<PostVS>) : Refresh()
366+
data class Error(val error: Throwable) : Refresh()
367+
object Refreshing : Refresh()
368+
369+
override fun reduce(vs: ViewState): ViewState {
370+
return when (this) {
371+
is Success -> {
372+
listOf(
373+
PhotoFirstPage.Data(photos),
374+
PostFirstPage.Data(posts)
375+
).fold(vs.copy(isRefreshing = false)) { acc, change -> change.reduce(acc) }
376+
}
377+
is Error -> vs.copy(isRefreshing = false)
378+
Refreshing -> vs.copy(isRefreshing = true)
379+
}
380+
}
381+
}
351382
}
352383

353384
sealed class SingleEvent {
@@ -371,5 +402,7 @@ interface MainContract {
371402

372403
fun postFirstPageChanges(limit: Int): Observable<PartialStateChange.PostFirstPage>
373404
fun postNextPageChanges(start: Int, limit: Int): Observable<PartialStateChange.PostNextPage>
405+
406+
fun refreshAll(limitPost: Int, limitPhoto: Int): Observable<PartialStateChange.Refresh>
374407
}
375408
}

app/src/main/java/com/hoc/pagination_mvi/ui/main/MainFragment.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.hoc.pagination_mvi.ui.main
22

33
import android.graphics.Rect
44
import android.os.Bundle
5+
import android.util.Log
56
import android.view.LayoutInflater
67
import android.view.MotionEvent
78
import android.view.View
@@ -17,6 +18,7 @@ import com.hoc.pagination_mvi.isOrientationPortrait
1718
import com.hoc.pagination_mvi.toast
1819
import com.hoc.pagination_mvi.ui.main.MainContract.ViewIntent
1920
import com.jakewharton.rxbinding3.recyclerview.scrollEvents
21+
import com.jakewharton.rxbinding3.swiperefreshlayout.refreshes
2022
import dagger.android.support.AndroidSupportInjection
2123
import io.reactivex.Observable
2224
import io.reactivex.ObservableSource
@@ -124,9 +126,18 @@ class MainFragment : Fragment() {
124126
}
125127

126128
private fun bindVM() {
127-
mainVM.stateD.observe(viewLifecycleOwner, Observer {
128-
it ?: return@Observer
129-
adapter.submitList(it.items)
129+
mainVM.stateD.observe(viewLifecycleOwner, Observer { vs ->
130+
vs ?: return@Observer
131+
Log.d("###", "${vs.isRefreshing} ${vs.enableRefresh}")
132+
133+
adapter.submitList(vs.items)
134+
135+
if (vs.isRefreshing) {
136+
swipe_refresh.post { swipe_refresh.isRefreshing = true }
137+
} else {
138+
swipe_refresh.isRefreshing = false
139+
}
140+
swipe_refresh.isEnabled = vs.enableRefresh
130141
})
131142
mainVM
132143
.singleEventObservable
@@ -137,6 +148,7 @@ class MainFragment : Fragment() {
137148
Observable.mergeArray(
138149
Observable.just(ViewIntent.Initial),
139150
loadNextPageIntent(),
151+
swipe_refresh.refreshes().map { ViewIntent.Refresh },
140152
adapter
141153
.retryObservable
142154
.throttleFirst(500, TimeUnit.MILLISECONDS)

app/src/main/java/com/hoc/pagination_mvi/ui/main/MainInteractorImpl.kt

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import com.hoc.pagination_mvi.di.ApplicationScope
44
import com.hoc.pagination_mvi.domain.dispatchers_schedulers.CoroutinesDispatchersProvider
55
import com.hoc.pagination_mvi.domain.usecase.GetPhotosUseCase
66
import com.hoc.pagination_mvi.domain.usecase.GetPostsUseCase
7+
import com.hoc.pagination_mvi.ui.main.MainContract.PartialStateChange.*
78
import com.hoc.pagination_mvi.ui.main.MainContract.PhotoVS
89
import com.hoc.pagination_mvi.ui.main.MainContract.PostVS
910
import io.reactivex.Observable
1011
import kotlinx.coroutines.ExperimentalCoroutinesApi
12+
import kotlinx.coroutines.async
13+
import kotlinx.coroutines.coroutineScope
1114
import kotlinx.coroutines.delay
1215
import kotlinx.coroutines.rx2.rxObservable
1316
import javax.inject.Inject
@@ -22,65 +25,91 @@ class MainInteractorImpl @Inject constructor(
2225
override fun photoNextPageChanges(
2326
start: Int,
2427
limit: Int
25-
): Observable<MainContract.PartialStateChange.PhotoNextPage> {
28+
): Observable<PhotoNextPage> {
2629
return rxObservable(dispatchers.main) {
27-
send(MainContract.PartialStateChange.PhotoNextPage.Loading)
30+
send(PhotoNextPage.Loading)
2831
try {
2932
getPhotosUseCase(start = start, limit = limit)
3033
.map(MainContract::PhotoVS)
31-
.let { MainContract.PartialStateChange.PhotoNextPage.Data(it) }
34+
.let { PhotoNextPage.Data(it) }
3235
.let { send(it) }
3336
} catch (e: Exception) {
3437
delay(500)
35-
send(MainContract.PartialStateChange.PhotoNextPage.Error(e))
38+
send(PhotoNextPage.Error(e))
3639
}
3740
}
3841
}
3942

40-
override fun photoFirstPageChanges(limit: Int): Observable<MainContract.PartialStateChange.PhotoFirstPage> {
43+
override fun photoFirstPageChanges(limit: Int): Observable<PhotoFirstPage> {
4144
return rxObservable(dispatchers.main) {
42-
send(MainContract.PartialStateChange.PhotoFirstPage.Loading)
45+
send(PhotoFirstPage.Loading)
4346
try {
4447
getPhotosUseCase(start = 0, limit = limit)
4548
.map(::PhotoVS)
46-
.let { MainContract.PartialStateChange.PhotoFirstPage.Data(it) }
49+
.let { PhotoFirstPage.Data(it) }
4750
.let { send(it) }
4851
} catch (e: Exception) {
4952
delay(500)
50-
send(MainContract.PartialStateChange.PhotoFirstPage.Error(e))
53+
send(PhotoFirstPage.Error(e))
5154
}
5255
}
5356
}
5457

55-
override fun postFirstPageChanges(limit: Int): Observable<MainContract.PartialStateChange.PostFirstPage> {
58+
override fun postFirstPageChanges(limit: Int): Observable<PostFirstPage> {
5659
return rxObservable(dispatchers.main) {
57-
send(MainContract.PartialStateChange.PostFirstPage.Loading)
60+
send(PostFirstPage.Loading)
5861
try {
5962
getPostsUseCase(start = 0, limit = limit)
6063
.map(::PostVS)
61-
.let { MainContract.PartialStateChange.PostFirstPage.Data(it) }
64+
.let { PostFirstPage.Data(it) }
6265
.let { send(it) }
6366
} catch (e: Exception) {
6467
delay(500)
65-
send(MainContract.PartialStateChange.PostFirstPage.Error(e))
68+
send(PostFirstPage.Error(e))
6669
}
6770
}
6871
}
6972

7073
override fun postNextPageChanges(
7174
start: Int,
7275
limit: Int
73-
): Observable<MainContract.PartialStateChange.PostNextPage> {
76+
): Observable<PostNextPage> {
7477
return rxObservable(dispatchers.main) {
75-
send(MainContract.PartialStateChange.PostNextPage.Loading)
78+
send(PostNextPage.Loading)
7679
try {
7780
getPostsUseCase(start = start, limit = limit)
7881
.map(::PostVS)
79-
.let { MainContract.PartialStateChange.PostNextPage.Data(it) }
82+
.let { PostNextPage.Data(it) }
8083
.let { send(it) }
8184
} catch (e: Exception) {
8285
delay(500)
83-
send(MainContract.PartialStateChange.PostNextPage.Error(e))
86+
send(PostNextPage.Error(e))
87+
}
88+
}
89+
}
90+
91+
override fun refreshAll(
92+
limitPost: Int,
93+
limitPhoto: Int
94+
): Observable<Refresh> {
95+
return rxObservable(dispatchers.main) {
96+
send(Refresh.Refreshing)
97+
98+
coroutineScope {
99+
val async1 = async { getPostsUseCase(limit = limitPost, start = 0) }
100+
val async2 = async { getPhotosUseCase(limit = limitPhoto, start = 0) }
101+
102+
try {
103+
send(
104+
Refresh.Success(
105+
posts = async1.await().map(::PostVS),
106+
photos = async2.await().map(::PhotoVS)
107+
)
108+
)
109+
} catch (e: Exception) {
110+
delay(500)
111+
send(Refresh.Error(e))
112+
}
84113
}
85114
}
86115
}

app/src/main/java/com/hoc/pagination_mvi/ui/main/MainVM.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,19 @@ class MainVM @Inject constructor(
9898
.exhaustMap { interactor.postFirstPageChanges(limit = POST_PAGE_SIZE) }
9999
}
100100

101+
private val refreshProcessor =
102+
ObservableTransformer<ViewIntent.Refresh, PartialStateChange> { intents ->
103+
intents
104+
.withLatestFrom(stateObservable)
105+
.filter { (_, vs) -> vs.enableRefresh }
106+
.exhaustMap {
107+
interactor.refreshAll(
108+
limitPhoto = PHOTO_PAGE_SIZE,
109+
limitPost = POST_PAGE_SIZE
110+
)
111+
}
112+
}
113+
101114
private val toPartialStateChange =
102115
ObservableTransformer<ViewIntent, PartialStateChange> { intents ->
103116
intents
@@ -112,7 +125,8 @@ class MainVM @Inject constructor(
112125
shared.ofType<ViewIntent.RetryLoadPageHorizontal>().compose(
113126
retryLoadPageHorizontalProcessor
114127
),
115-
shared.ofType<ViewIntent.RetryHorizontal>().compose(retryHorizontalProcessor)
128+
shared.ofType<ViewIntent.RetryHorizontal>().compose(retryHorizontalProcessor),
129+
shared.ofType<ViewIntent.Refresh>().compose(refreshProcessor)
116130
)
117131
}
118132
.compose(sendSingleEvent)

app/src/main/res/layout/fragment_main.xml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,16 @@
55
android:layout_width="match_parent"
66
android:layout_height="match_parent">
77

8-
<androidx.recyclerview.widget.RecyclerView
9-
android:id="@+id/recycler"
8+
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
9+
android:id="@+id/swipe_refresh"
1010
android:layout_width="match_parent"
11-
android:layout_height="match_parent"
12-
android:scrollbars="vertical" />
11+
android:layout_height="match_parent">
12+
13+
<androidx.recyclerview.widget.RecyclerView
14+
android:id="@+id/recycler"
15+
android:layout_width="match_parent"
16+
android:layout_height="match_parent"
17+
android:scrollbars="vertical" />
18+
19+
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
1320
</androidx.constraintlayout.widget.ConstraintLayout>

0 commit comments

Comments
 (0)