Skip to content

Commit e70148f

Browse files
committed
handle single event
1 parent 8e255dd commit e70148f

File tree

4 files changed

+114
-16
lines changed

4 files changed

+114
-16
lines changed

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

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,16 @@ interface MainContract {
123123

124124
sealed class ViewIntent {
125125
object Initial : ViewIntent()
126+
object Refresh : ViewIntent()
127+
128+
// Vertical
126129
object LoadNextPage : ViewIntent()
130+
127131
object RetryLoadPage : ViewIntent()
128132

133+
// Horizontal
129134
object LoadNextPageHorizontal : ViewIntent()
135+
130136
object RetryLoadPageHorizontal : ViewIntent()
131137
}
132138

@@ -174,9 +180,13 @@ interface MainContract {
174180
vs.items.filterIsInstance<Item.Photo>() + this.photos.map { Item.Photo(it) }
175181

176182
vs.copy(
177-
items = vs.items.filter { it !is Item.Photo && it !is Item.Placeholder }
178-
+ photoItems
179-
+ Item.Placeholder(PlaceholderState.Idle),
183+
items = vs.items.filter { it !is Item.Photo && it !is Item.Placeholder } +
184+
photoItems +
185+
if (this.photos.isNotEmpty()) {
186+
listOf(Item.Placeholder(PlaceholderState.Idle))
187+
} else {
188+
emptyList()
189+
},
180190
photoItems = photoItems
181191
)
182192
}
@@ -270,7 +280,11 @@ interface MainContract {
270280
val postItems = item.items.filterIsInstance<HorizontalItem.Post>() +
271281
this.posts.map { HorizontalItem.Post(it) }
272282
item.copy(
273-
items = postItems + HorizontalItem.Placeholder(PlaceholderState.Idle),
283+
items = postItems + if (this.posts.isNotEmpty()) {
284+
listOf(HorizontalItem.Placeholder(PlaceholderState.Idle))
285+
} else {
286+
emptyList()
287+
},
274288
postItems = postItems
275289
)
276290
} else {
@@ -315,7 +329,18 @@ interface MainContract {
315329
}
316330

317331
sealed class SingleEvent {
332+
object RefreshSuccess : SingleEvent()
333+
data class RefreshFailure(val error: Throwable) : SingleEvent()
334+
335+
// Horizontal
336+
data class GetPostsFailure(val error: Throwable) : SingleEvent()
337+
338+
object HasReachedMaxHorizontal : SingleEvent()
339+
340+
// Vertical
341+
data class GetPhotosFailure(val error: Throwable) : SingleEvent()
318342

343+
object HasReachedMax : SingleEvent()
319344
}
320345

321346
interface Interactor {

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

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

33
import android.graphics.Rect
44
import android.os.Bundle
5-
import android.util.Log
65
import android.view.LayoutInflater
76
import android.view.View
87
import android.view.ViewGroup
@@ -14,13 +13,15 @@ import androidx.recyclerview.widget.GridLayoutManager
1413
import androidx.recyclerview.widget.RecyclerView
1514
import com.hoc.pagination_mvi.R
1615
import com.hoc.pagination_mvi.isOrientationPortrait
16+
import com.hoc.pagination_mvi.toast
1717
import com.hoc.pagination_mvi.ui.main.MainContract.ViewIntent
1818
import com.jakewharton.rxbinding3.recyclerview.scrollEvents
1919
import dagger.android.support.AndroidSupportInjection
2020
import io.reactivex.Observable
2121
import io.reactivex.ObservableSource
2222
import io.reactivex.disposables.CompositeDisposable
2323
import io.reactivex.rxkotlin.addTo
24+
import io.reactivex.rxkotlin.subscribeBy
2425
import kotlinx.android.synthetic.main.fragment_main.*
2526
import kotlinx.coroutines.ExperimentalCoroutinesApi
2627
import java.util.concurrent.TimeUnit
@@ -114,9 +115,12 @@ class MainFragment : Fragment() {
114115
mainVM.stateD.observe(viewLifecycleOwner, Observer {
115116
it ?: return@Observer
116117
adapter.submitList(it.items)
117-
118-
Log.d("###", "[LAST] ${it.items.lastOrNull()}")
119118
})
119+
mainVM
120+
.singleEventObservable
121+
.subscribeBy(onNext = ::handleSingleEvent)
122+
.addTo(compositeDisposable)
123+
120124
mainVM.processIntents(
121125
Observable.mergeArray(
122126
Observable.just(ViewIntent.Initial),
@@ -132,6 +136,35 @@ class MainFragment : Fragment() {
132136
).addTo(compositeDisposable)
133137
}
134138

139+
private fun handleSingleEvent(event: MainContract.SingleEvent) {
140+
return when (event) {
141+
MainContract.SingleEvent.RefreshSuccess -> {
142+
toast("Refresh success")
143+
}
144+
is MainContract.SingleEvent.RefreshFailure -> {
145+
toast(
146+
"Refresh failure: ${event.error.message ?: ""}"
147+
)
148+
}
149+
is MainContract.SingleEvent.GetPostsFailure -> {
150+
toast(
151+
"Get posts failure: ${event.error.message ?: ""}"
152+
)
153+
}
154+
MainContract.SingleEvent.HasReachedMaxHorizontal -> {
155+
toast("Got all posts")
156+
}
157+
is MainContract.SingleEvent.GetPhotosFailure -> {
158+
toast(
159+
"Get photos failure: ${event.error.message ?: ""}"
160+
)
161+
}
162+
MainContract.SingleEvent.HasReachedMax -> {
163+
toast("Got all photos")
164+
}
165+
}
166+
}
167+
135168
private fun loadNextPageIntent(): ObservableSource<ViewIntent> {
136169
return recycler
137170
.scrollEvents()

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

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.hoc.pagination_mvi.asObservable
77
import com.hoc.pagination_mvi.domain.dispatchers_schedulers.RxSchedulerProvider
88
import com.hoc.pagination_mvi.exhaustMap
99
import com.hoc.pagination_mvi.ui.main.MainContract.*
10+
import com.hoc.pagination_mvi.ui.main.MainContract.PartialStateChange.*
1011
import io.reactivex.Observable
1112
import io.reactivex.ObservableTransformer
1213
import io.reactivex.disposables.CompositeDisposable
@@ -25,14 +26,18 @@ class MainVM @Inject constructor(
2526
private val rxSchedulerProvider: RxSchedulerProvider
2627
) : ViewModel() {
2728
private val initial = ViewState.initial()
29+
2830
private val _stateD = MutableLiveData<ViewState>().apply { value = initial }
2931
private val stateS = BehaviorSubject.createDefault(initial)
30-
3132
private val stateObservable get() = stateS.asObservable()
33+
3234
private val intentS = PublishSubject.create<ViewIntent>()
35+
private val singleEventS = PublishSubject.create<SingleEvent>()
3336
private val compositeDisposable = CompositeDisposable()
3437

38+
/// Expose view state live data & single event observable
3539
val stateD get() = _stateD.distinctUntilChanged()
40+
val singleEventObservable get() = singleEventS.asObservable()
3641

3742
fun processIntents(intents: Observable<ViewIntent>) = intents.subscribe(intentS::onNext)!!
3843

@@ -78,14 +83,43 @@ class MainVM @Inject constructor(
7883

7984
private val toPartialStateChange =
8085
ObservableTransformer<ViewIntent, PartialStateChange> { intents ->
81-
intents.publish { shared ->
82-
Observable.mergeArray(
83-
shared.ofType<ViewIntent.Initial>().compose(initialProcessor),
84-
shared.ofType<ViewIntent.LoadNextPage>().compose(nextPageProcessor),
85-
shared.ofType<ViewIntent.RetryLoadPage>().compose(retryLoadPageProcessor),
86-
shared.ofType<ViewIntent.LoadNextPageHorizontal>().compose(loadNextPageHorizontalProcessor)
87-
)
88-
}
86+
intents
87+
.publish { shared ->
88+
Observable.mergeArray(
89+
shared.ofType<ViewIntent.Initial>().compose(initialProcessor),
90+
shared.ofType<ViewIntent.LoadNextPage>().compose(nextPageProcessor),
91+
shared.ofType<ViewIntent.RetryLoadPage>().compose(retryLoadPageProcessor),
92+
shared.ofType<ViewIntent.LoadNextPageHorizontal>().compose(
93+
loadNextPageHorizontalProcessor
94+
)
95+
)
96+
}
97+
.compose(sendSingleEvent)
98+
}
99+
100+
private val sendSingleEvent =
101+
ObservableTransformer<PartialStateChange, PartialStateChange> { changes ->
102+
changes
103+
.observeOn(rxSchedulerProvider.main)
104+
.doOnNext { change ->
105+
when (change) {
106+
is PhotoFirstPage.Data -> if (change.photos.isEmpty()) singleEventS.onNext(SingleEvent.HasReachedMax)
107+
is PhotoFirstPage.Error -> singleEventS.onNext(SingleEvent.GetPhotosFailure(change.error))
108+
PhotoFirstPage.Loading -> Unit
109+
///
110+
is PhotoNextPage.Data -> if (change.photos.isEmpty()) singleEventS.onNext(SingleEvent.HasReachedMax)
111+
is PhotoNextPage.Error -> singleEventS.onNext(SingleEvent.GetPhotosFailure(change.error))
112+
PhotoNextPage.Loading -> Unit
113+
///
114+
is PostFirstPage.Data -> if (change.posts.isEmpty()) singleEventS.onNext(SingleEvent.HasReachedMaxHorizontal)
115+
is PostFirstPage.Error -> singleEventS.onNext(SingleEvent.GetPostsFailure(change.error))
116+
PostFirstPage.Loading -> Unit
117+
///
118+
is PostNextPage.Data -> if (change.posts.isEmpty()) singleEventS.onNext(SingleEvent.HasReachedMaxHorizontal)
119+
is PostNextPage.Error -> singleEventS.onNext(SingleEvent.GetPostsFailure(change.error))
120+
PostNextPage.Loading -> Unit
121+
}
122+
}
89123
}
90124

91125
init {

app/src/main/java/com/hoc/pagination_mvi/util.kt

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

33
import android.content.Context
44
import android.content.res.Configuration
5+
import android.widget.Toast
56
import androidx.annotation.CheckResult
7+
import androidx.fragment.app.Fragment
68
import io.reactivex.BackpressureStrategy
79
import io.reactivex.Observable
810
import io.reactivex.subjects.Subject
@@ -19,3 +21,7 @@ inline fun <T : Any, R : Any> Observable<T>.exhaustMap(crossinline transform: (T
1921
inline fun <T> Subject<T>.asObservable(): Observable<T> = this
2022

2123
val Context.isOrientationPortrait get() = this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
24+
25+
fun Context.toast(text: CharSequence) = Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
26+
27+
fun Fragment.toast(text: CharSequence) = requireContext().toast(text)

0 commit comments

Comments
 (0)