Skip to content

Commit 565f3af

Browse files
committed
better
1 parent 8a0439c commit 565f3af

File tree

4 files changed

+141
-106
lines changed

4 files changed

+141
-106
lines changed

app/src/main/java/com/hoc081098/paginationmviflow/ui/main/MainModule.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ package com.hoc081098.paginationmviflow.ui.main
33
import dagger.Binds
44
import dagger.Module
55
import dagger.hilt.InstallIn
6-
import dagger.hilt.android.components.ActivityRetainedComponent
6+
import dagger.hilt.android.components.ViewModelComponent
77

88
@Module
9-
@InstallIn(ActivityRetainedComponent::class)
9+
@InstallIn(ViewModelComponent::class)
1010
interface MainModule {
1111
@Binds
1212
fun provideMainInteractor(impl: MainInteractorImpl): MainContract.Interactor
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package com.hoc081098.paginationmviflow.ui.main
2+
3+
import com.hoc081098.flowext.flatMapFirst
4+
import com.hoc081098.flowext.withLatestFrom
5+
import com.hoc081098.paginationmviflow.FlowTransformer
6+
import kotlinx.coroutines.ExperimentalCoroutinesApi
7+
import kotlinx.coroutines.FlowPreview
8+
import kotlinx.coroutines.flow.StateFlow
9+
import kotlinx.coroutines.flow.filter
10+
import kotlinx.coroutines.flow.flatMapMerge
11+
import kotlinx.coroutines.flow.map
12+
import kotlinx.coroutines.flow.merge
13+
import javax.inject.Inject
14+
import com.hoc081098.paginationmviflow.ui.main.MainContract.ViewIntent as VI
15+
import com.hoc081098.paginationmviflow.ui.main.MainContract.ViewState as VS
16+
17+
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
18+
class MainProcessors @Inject constructor(
19+
private val interactor: MainContract.Interactor,
20+
) {
21+
internal fun getInitialProcessor(stateFlow: StateFlow<VS>): FlowTransformer<VI.Initial, MainContract.PartialStateChange> =
22+
FlowTransformer { intents ->
23+
intents
24+
.withLatestFrom(stateFlow)
25+
.filter { (_, vs) -> vs.photoItems.isEmpty() }
26+
.flatMapMerge {
27+
merge(
28+
interactor.photoFirstPageChanges(limit = MainVM.PHOTO_PAGE_SIZE),
29+
interactor.postFirstPageChanges(limit = MainVM.POST_PAGE_SIZE)
30+
)
31+
}
32+
}
33+
34+
internal fun getNextPageProcessor(stateFlow: StateFlow<VS>): FlowTransformer<VI.LoadNextPage, MainContract.PartialStateChange> =
35+
FlowTransformer { intents ->
36+
intents
37+
.withLatestFrom(stateFlow)
38+
.filter { (_, vs) -> vs.canLoadNextPage() }
39+
.map { (_, vs) -> vs.photoItems.size }
40+
.flatMapFirst {
41+
interactor.photoNextPageChanges(
42+
start = it,
43+
limit = MainVM.PHOTO_PAGE_SIZE
44+
)
45+
}
46+
}
47+
48+
internal fun getRetryLoadPageProcessor(stateFlow: StateFlow<VS>): FlowTransformer<VI.RetryLoadPage, MainContract.PartialStateChange> =
49+
FlowTransformer { intents ->
50+
intents
51+
.withLatestFrom(stateFlow)
52+
.filter { (_, vs) -> vs.shouldRetry() }
53+
.map { (_, vs) -> vs.photoItems.size }
54+
.flatMapFirst {
55+
interactor.photoNextPageChanges(
56+
start = it,
57+
limit = MainVM.PHOTO_PAGE_SIZE
58+
)
59+
}
60+
}
61+
62+
internal fun getLoadNextPageHorizontalProcessor(stateFlow: StateFlow<VS>): FlowTransformer<VI.LoadNextPageHorizontal, MainContract.PartialStateChange> =
63+
FlowTransformer { intents ->
64+
intents
65+
.withLatestFrom(stateFlow)
66+
.filter { (_, vs) -> vs.canLoadNextPageHorizontal() }
67+
.map { (_, vs) -> vs.getHorizontalListCount() }
68+
.flatMapFirst { interactor.postNextPageChanges(start = it, limit = MainVM.POST_PAGE_SIZE) }
69+
}
70+
71+
internal fun getRetryLoadPageHorizontalProcessor(stateFlow: StateFlow<VS>): FlowTransformer<VI.RetryLoadPageHorizontal, MainContract.PartialStateChange> =
72+
FlowTransformer { intents ->
73+
intents
74+
.withLatestFrom(stateFlow)
75+
.filter { (_, vs) -> vs.shouldRetryNextPageHorizontal() }
76+
.map { (_, vs) -> vs.getHorizontalListCount() }
77+
.flatMapFirst { interactor.postNextPageChanges(start = it, limit = MainVM.POST_PAGE_SIZE) }
78+
}
79+
80+
internal fun getRetryHorizontalProcessor(stateFlow: StateFlow<VS>): FlowTransformer<VI.RetryHorizontal, MainContract.PartialStateChange> =
81+
FlowTransformer { intents ->
82+
intents
83+
.withLatestFrom(stateFlow)
84+
.filter { (_, vs) -> vs.shouldRetryHorizontal() }
85+
.flatMapFirst { interactor.postFirstPageChanges(limit = MainVM.POST_PAGE_SIZE) }
86+
}
87+
88+
internal fun getRefreshProcessor(stateFlow: StateFlow<VS>): FlowTransformer<VI.Refresh, MainContract.PartialStateChange> =
89+
FlowTransformer { intents ->
90+
intents
91+
.withLatestFrom(stateFlow)
92+
.filter { (_, vs) -> vs.enableRefresh }
93+
.flatMapFirst {
94+
interactor.refreshAll(
95+
limitPhoto = MainVM.PHOTO_PAGE_SIZE,
96+
limitPost = MainVM.POST_PAGE_SIZE
97+
)
98+
}
99+
}
100+
}

app/src/main/java/com/hoc081098/paginationmviflow/ui/main/MainVM.kt

Lines changed: 32 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ package com.hoc081098.paginationmviflow.ui.main
22

33
import androidx.lifecycle.ViewModel
44
import androidx.lifecycle.viewModelScope
5-
import com.hoc081098.flowext.flatMapFirst
6-
import com.hoc081098.flowext.withLatestFrom
75
import com.hoc081098.paginationmviflow.FlowTransformer
8-
import com.hoc081098.paginationmviflow.ui.main.MainContract.Interactor
6+
import com.hoc081098.paginationmviflow.pipe
97
import com.hoc081098.paginationmviflow.ui.main.MainContract.PartialStateChange
108
import com.hoc081098.paginationmviflow.ui.main.MainContract.PartialStateChange.PhotoFirstPage
119
import com.hoc081098.paginationmviflow.ui.main.MainContract.PartialStateChange.PhotoNextPage
@@ -24,9 +22,7 @@ import kotlinx.coroutines.flow.StateFlow
2422
import kotlinx.coroutines.flow.asStateFlow
2523
import kotlinx.coroutines.flow.filter
2624
import kotlinx.coroutines.flow.filterIsInstance
27-
import kotlinx.coroutines.flow.flatMapMerge
2825
import kotlinx.coroutines.flow.launchIn
29-
import kotlinx.coroutines.flow.map
3026
import kotlinx.coroutines.flow.merge
3127
import kotlinx.coroutines.flow.onEach
3228
import kotlinx.coroutines.flow.receiveAsFlow
@@ -43,7 +39,9 @@ import com.hoc081098.paginationmviflow.ui.main.MainContract.ViewState as VS
4339
FlowPreview::class
4440
)
4541
@HiltViewModel
46-
class MainVM @Inject constructor(private val interactor: Interactor) : ViewModel() {
42+
class MainVM @Inject constructor(
43+
private val mainProcessors: MainProcessors,
44+
) : ViewModel() {
4745
private val initialVS = VS.initial()
4846

4947
private val _stateSF = MutableStateFlow(initialVS)
@@ -55,121 +53,52 @@ class MainVM @Inject constructor(private val interactor: Interactor) : ViewModel
5553

5654
suspend fun processIntent(intent: VI) = _intentSF.emit(intent)
5755

58-
private val initialProcessor:
59-
FlowTransformer<VI.Initial, PartialStateChange> = { intents ->
56+
private val toPartialStateChange: FlowTransformer<VI, PartialStateChange> =
57+
FlowTransformer { intents ->
6058
intents
61-
.withLatestFrom(_stateSF)
62-
.filter { (_, vs) -> vs.photoItems.isEmpty() }
63-
.flatMapMerge {
59+
.shareIn(viewModelScope, SharingStarted.WhileSubscribed())
60+
.let { shared ->
6461
merge(
65-
interactor.photoFirstPageChanges(limit = PHOTO_PAGE_SIZE),
66-
interactor.postFirstPageChanges(limit = POST_PAGE_SIZE)
62+
shared.filterIsInstance<VI.Initial>()
63+
.pipe(mainProcessors.getInitialProcessor(stateFlow)),
64+
shared.filterIsInstance<VI.LoadNextPage>()
65+
.pipe(mainProcessors.getNextPageProcessor(stateFlow)),
66+
shared.filterIsInstance<VI.RetryLoadPage>()
67+
.pipe(mainProcessors.getRetryLoadPageProcessor(stateFlow)),
68+
shared.filterIsInstance<VI.LoadNextPageHorizontal>()
69+
.pipe(mainProcessors.getLoadNextPageHorizontalProcessor(stateFlow)),
70+
shared.filterIsInstance<VI.RetryLoadPageHorizontal>()
71+
.pipe(mainProcessors.getRetryLoadPageHorizontalProcessor(stateFlow)),
72+
shared.filterIsInstance<VI.RetryHorizontal>()
73+
.pipe(mainProcessors.getRetryHorizontalProcessor(stateFlow)),
74+
shared.filterIsInstance<VI.Refresh>()
75+
.pipe(mainProcessors.getRefreshProcessor(stateFlow))
6776
)
6877
}
78+
.pipe(sendSingleEvent)
6979
}
7080

71-
private val nextPageProcessor: FlowTransformer<VI.LoadNextPage, PartialStateChange> =
72-
{ intents ->
73-
intents
74-
.withLatestFrom(_stateSF)
75-
.filter { (_, vs) -> vs.canLoadNextPage() }
76-
.map { (_, vs) -> vs.photoItems.size }
77-
.flatMapFirst { interactor.photoNextPageChanges(start = it, limit = PHOTO_PAGE_SIZE) }
78-
}
79-
80-
private val retryLoadPageProcessor: FlowTransformer<VI.RetryLoadPage, PartialStateChange> =
81-
{ intents ->
82-
intents
83-
.withLatestFrom(_stateSF)
84-
.filter { (_, vs) -> vs.shouldRetry() }
85-
.map { (_, vs) -> vs.photoItems.size }
86-
.flatMapFirst { interactor.photoNextPageChanges(start = it, limit = PHOTO_PAGE_SIZE) }
87-
}
88-
89-
private val loadNextPageHorizontalProcessor: FlowTransformer<VI.LoadNextPageHorizontal, PartialStateChange> =
90-
{ intents ->
91-
intents
92-
.withLatestFrom(_stateSF)
93-
.filter { (_, vs) -> vs.canLoadNextPageHorizontal() }
94-
.map { (_, vs) -> vs.getHorizontalListCount() }
95-
.flatMapFirst { interactor.postNextPageChanges(start = it, limit = POST_PAGE_SIZE) }
96-
}
97-
98-
private val retryLoadPageHorizontalProcessor: FlowTransformer<VI.RetryLoadPageHorizontal, PartialStateChange> =
99-
{ intents ->
100-
intents
101-
.withLatestFrom(_stateSF)
102-
.filter { (_, vs) -> vs.shouldRetryNextPageHorizontal() }
103-
.map { (_, vs) -> vs.getHorizontalListCount() }
104-
.flatMapFirst { interactor.postNextPageChanges(start = it, limit = POST_PAGE_SIZE) }
105-
}
106-
107-
private val retryHorizontalProcessor: FlowTransformer<VI.RetryHorizontal, PartialStateChange> =
108-
{ intents ->
109-
intents
110-
.withLatestFrom(_stateSF)
111-
.filter { (_, vs) -> vs.shouldRetryHorizontal() }
112-
.flatMapFirst { interactor.postFirstPageChanges(limit = POST_PAGE_SIZE) }
113-
}
114-
115-
private val refreshProcessor: FlowTransformer<VI.Refresh, PartialStateChange> =
116-
{ intents ->
117-
intents
118-
.withLatestFrom(_stateSF)
119-
.filter { (_, vs) -> vs.enableRefresh }
120-
.flatMapFirst {
121-
interactor.refreshAll(
122-
limitPhoto = PHOTO_PAGE_SIZE,
123-
limitPost = POST_PAGE_SIZE
124-
)
125-
}
126-
}
127-
128-
private val toPartialStateChange: FlowTransformer<VI, PartialStateChange> = { intents ->
129-
intents
130-
.shareIn(viewModelScope, SharingStarted.WhileSubscribed())
131-
.let { shared ->
132-
merge(
133-
shared.filterIsInstance<VI.Initial>()
134-
.let(initialProcessor),
135-
shared.filterIsInstance<VI.LoadNextPage>()
136-
.let(nextPageProcessor),
137-
shared.filterIsInstance<VI.RetryLoadPage>()
138-
.let(retryLoadPageProcessor),
139-
shared.filterIsInstance<VI.LoadNextPageHorizontal>()
140-
.let(loadNextPageHorizontalProcessor),
141-
shared.filterIsInstance<VI.RetryLoadPageHorizontal>()
142-
.let(retryLoadPageHorizontalProcessor),
143-
shared.filterIsInstance<VI.RetryHorizontal>()
144-
.let(retryHorizontalProcessor),
145-
shared.filterIsInstance<VI.Refresh>()
146-
.let(refreshProcessor)
147-
)
148-
}
149-
.let(sendSingleEvent)
150-
}
151-
15281
private val sendSingleEvent: FlowTransformer<PartialStateChange, PartialStateChange> =
153-
{ changes ->
82+
FlowTransformer { changes ->
15483
changes
15584
.onEach { change ->
15685
when (change) {
15786
is PhotoFirstPage.Data -> if (change.photos.isEmpty()) _singleEventChannel.send(SE.HasReachedMax)
15887
is PhotoFirstPage.Error -> _singleEventChannel.send(SE.GetPhotosFailure(change.error))
15988
PhotoFirstPage.Loading -> Unit
160-
// /
89+
//
16190
is PhotoNextPage.Data -> if (change.photos.isEmpty()) _singleEventChannel.send(SE.HasReachedMax)
16291
is PhotoNextPage.Error -> _singleEventChannel.send(SE.GetPhotosFailure(change.error))
16392
PhotoNextPage.Loading -> Unit
164-
// /
93+
//
16594
is PostFirstPage.Data -> if (change.posts.isEmpty()) _singleEventChannel.send(SE.HasReachedMaxHorizontal)
16695
is PostFirstPage.Error -> _singleEventChannel.send(SE.GetPostsFailure(change.error))
16796
PostFirstPage.Loading -> Unit
168-
// /
97+
//
16998
is PostNextPage.Data -> if (change.posts.isEmpty()) _singleEventChannel.send(SE.HasReachedMaxHorizontal)
17099
is PostNextPage.Error -> _singleEventChannel.send(SE.GetPostsFailure(change.error))
171100
PostNextPage.Loading -> Unit
172-
// /
101+
//
173102
is Refresh.Success -> _singleEventChannel.send(SE.RefreshSuccess)
174103
is Refresh.Error -> _singleEventChannel.send(SE.RefreshFailure(change.error))
175104
Refresh.Refreshing -> Unit
@@ -179,15 +108,15 @@ class MainVM @Inject constructor(private val interactor: Interactor) : ViewModel
179108

180109
init {
181110
_intentSF
182-
.let(intentFilterer)
183-
.let(toPartialStateChange)
111+
.pipe(intentFilterer)
112+
.pipe(toPartialStateChange)
184113
.scan(initialVS) { vs, change -> change.reduce(vs) }
185114
.onEach { _stateSF.value = it }
186115
.launchIn(viewModelScope)
187116
}
188117

189-
private companion object {
190-
val intentFilterer: FlowTransformer<VI, VI> = { intents ->
118+
internal companion object {
119+
val intentFilterer: FlowTransformer<VI, VI> = FlowTransformer { intents ->
191120
merge(
192121
intents.filterIsInstance<VI.Initial>().take(1),
193122
intents.filter { it !is VI.Initial }

app/src/main/java/com/hoc081098/paginationmviflow/util.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ import kotlinx.coroutines.flow.callbackFlow
2121
import kotlinx.coroutines.flow.collect
2222
import kotlinx.coroutines.launch
2323

24-
typealias FlowTransformer<I, O> = (Flow<I>) -> Flow<O>
24+
fun interface FlowTransformer<I, O> {
25+
fun transform(input: Flow<I>): Flow<O>
26+
}
27+
28+
@Suppress("NOTHING_TO_INLINE")
29+
inline fun <I, O> Flow<I>.pipe(transformer: FlowTransformer<I, O>) =
30+
transformer.transform(this)
2531

2632
val Context.isOrientationPortrait get() = this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
2733

0 commit comments

Comments
 (0)