Skip to content

Commit 009b687

Browse files
PiotrBandurskiVAArkadiusz
authored andcommitted
feat(mvi-presenter) Added onError method in presenter to be called when exception occurs
1 parent 5b30b88 commit 009b687

File tree

2 files changed

+65
-6
lines changed

2 files changed

+65
-6
lines changed

mvi-presenter/src/main/java/pl/valueadd/mvi/presenter/BaseMviPresenter.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ abstract class BaseMviPresenter<VS : IBaseViewState, PS : IBasePartialState, VI
2525
* Hot observable of [view state][IBaseViewState].
2626
*/
2727
val stateObservable: Observable<VS>
28-
get() = viewStateBehaviorSubject
28+
get() = viewStateBehaviorSubject.share()
2929

3030
/**
3131
* Returns [view][IBaseView] but may throw [ViewNotAttachedException] if called in wrong place.
@@ -37,9 +37,8 @@ abstract class BaseMviPresenter<VS : IBaseViewState, PS : IBasePartialState, VI
3737
* @see subscribeViewStateConsumer
3838
* @see viewStateConsumerDisposable
3939
*/
40-
protected open val viewStateSubscriptionScheduler: Scheduler by lazy {
40+
protected open val viewStateSubscriptionScheduler: Scheduler =
4141
Schedulers.io()
42-
}
4342

4443
/**
4544
* @see subscribeViewStateConsumer
@@ -76,7 +75,7 @@ abstract class BaseMviPresenter<VS : IBaseViewState, PS : IBasePartialState, VI
7675
/**
7776
* A subject to pass emission of wrapped intents to currently binded view's consumer.
7877
*/
79-
private val viewStateBehaviorSubject by lazy {
78+
private val viewStateBehaviorSubject: BehaviorSubject<VS> by lazy {
8079
BehaviorSubject.createDefault(currentState)
8180
}
8281

@@ -179,6 +178,14 @@ abstract class BaseMviPresenter<VS : IBaseViewState, PS : IBasePartialState, VI
179178
*/
180179
protected open fun providePresenterIntents(): List<Observable<out PS>> = listOf()
181180

181+
/**
182+
* Method which is called when an Exception occurred during observing external
183+
* observables or reducing states.
184+
*/
185+
protected open fun onError(throwable: Throwable) {
186+
// no-op
187+
}
188+
182189
/**
183190
* Binds provided view's intents to wrapper subject.
184191
*
@@ -217,6 +224,7 @@ abstract class BaseMviPresenter<VS : IBaseViewState, PS : IBasePartialState, VI
217224
viewStateReducerDisposable =
218225
currentViewIntentsSubject
219226
.flatMap(::mapViewIntentToPartialState)
227+
.doOnError(::onError)
220228
.mergeWith(Observable.merge(providePresenterIntents()))
221229
.scan(currentState, this::reduce)
222230
.distinctUntilChanged()

mvi-presenter/src/test/java/pl/valueadd/mvi/presenter/BaseMviPresenterTest.kt

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ class BaseMviPresenterTest {
2323
@MockK
2424
private lateinit var mockReducer: TestReducer
2525

26+
@MockK
27+
private lateinit var mockTestLogger: TestErrorLogger
28+
2629
private lateinit var presenter: TestPresenter
2730

2831
private lateinit var presenterPublishSubject: PublishSubject<TestPartialState>
@@ -33,7 +36,8 @@ class BaseMviPresenterTest {
3336
presenter = TestPresenter(
3437
mockMapper,
3538
mockReducer,
36-
presenterPublishSubject
39+
presenterPublishSubject,
40+
mockTestLogger
3741
)
3842
}
3943

@@ -215,6 +219,42 @@ class BaseMviPresenterTest {
215219
assert(presenterPublishSubject.hasObservers() == false)
216220
}
217221

222+
@Test
223+
fun `Should call onError when error occurs during observing presenter intents`() {
224+
// Given
225+
val viewIntentsSubject = PublishSubject.create<TestViewIntent>()
226+
val mockView = createMockView(viewIntentsSubject)
227+
val mockThrowable: Throwable = mockk(relaxed = true)
228+
every { mockThrowable.stackTrace } returns emptyArray()
229+
every { mockThrowable.cause } returns null
230+
every { mockTestLogger.logError(any()) } returns Unit
231+
presenter.attachView(mockView)
232+
233+
// When
234+
presenterPublishSubject.onError(mockThrowable)
235+
236+
// Then
237+
verify(exactly = 1) { mockTestLogger.logError(mockThrowable) }
238+
}
239+
240+
@Test
241+
fun `Should call onError when error occurs during observing view intents`() {
242+
// Given
243+
val viewIntentsSubject = PublishSubject.create<TestViewIntent>()
244+
val mockView = createMockView(viewIntentsSubject)
245+
val mockThrowable: Throwable = mockk(relaxed = true)
246+
every { mockThrowable.stackTrace } returns emptyArray()
247+
every { mockThrowable.cause } returns null
248+
every { mockTestLogger.logError(any()) } returns Unit
249+
presenter.attachView(mockView)
250+
251+
// When
252+
viewIntentsSubject.onError(mockThrowable)
253+
254+
// Then
255+
verify(exactly = 1) { mockTestLogger.logError(mockThrowable) }
256+
}
257+
218258
private fun createMockView(viewIntentsObservable: Observable<TestViewIntent>): IBaseView<TestViewState, TestViewIntent> {
219259
return mockk {
220260
every { provideViewIntents() } returns listOf(viewIntentsObservable)
@@ -227,7 +267,8 @@ class BaseMviPresenterTest {
227267
private class TestPresenter(
228268
private val mapper: TestViewIntentToPartialStateMapper,
229269
private val reducer: TestReducer,
230-
private val presenterObservable: Observable<TestPartialState>
270+
private val presenterObservable: Observable<TestPartialState>,
271+
private val testErrorLogger: TestErrorLogger
231272
) : BaseMviPresenter<TestViewState, TestPartialState, TestViewIntent, IBaseView<TestViewState, TestViewIntent>>(Schedulers.trampoline()) {
232273
override val viewStateSubscriptionScheduler = Schedulers.trampoline()
233274
override val viewStateObservationScheduler = Schedulers.trampoline()
@@ -245,6 +286,10 @@ private class TestPresenter(
245286
override fun providePresenterIntents(): List<Observable<out TestPartialState>> {
246287
return listOf(presenterObservable)
247288
}
289+
290+
override fun onError(throwable: Throwable) {
291+
testErrorLogger.logError(throwable)
292+
}
248293
}
249294

250295
private class TestViewState(var someProperty: Int = 0) :
@@ -265,4 +310,10 @@ private class TestReducer {
265310
fun reduce(previousState: TestViewState, action: TestPartialState): TestViewState {
266311
throw RuntimeException("Should be mocked")
267312
}
313+
}
314+
315+
private class TestErrorLogger {
316+
fun logError(throwable: Throwable) {
317+
throw RuntimeException("Should be mocked")
318+
}
268319
}

0 commit comments

Comments
 (0)