Skip to content

Commit 662eb69

Browse files
committed
fix(main): revert swiping when failed to delete user
1 parent d078aa5 commit 662eb69

File tree

6 files changed

+80
-47
lines changed

6 files changed

+80
-47
lines changed

feature-main/src/main/java/com/hoc/flowmvi/ui/main/MainActivity.kt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,7 @@ class MainActivity :
8989
is SingleEvent.RemoveUser.Success -> toast("Removed '${event.user.fullName}'")
9090
is SingleEvent.RemoveUser.Failure -> {
9191
toast("Error when removing '${event.user.fullName}'")
92-
userAdapter.notifyItemChanged(
93-
vm.viewState.value
94-
.userItems
95-
.indexOfFirst { it.id == event.user.id }
96-
.takeIf { it != RecyclerView.NO_POSITION }
97-
?: return
98-
)
92+
userAdapter.notifyItemChanged(event.indexProducer() ?: return)
9993
}
10094
}
10195
}

feature-main/src/main/java/com/hoc/flowmvi/ui/main/MainContract.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ sealed interface SingleEvent : MviSingleEvent {
114114

115115
sealed interface RemoveUser : SingleEvent {
116116
data class Success(val user: UserItem) : RemoveUser
117-
data class Failure(val user: UserItem, val error: UserError) : RemoveUser
117+
data class Failure(
118+
val user: UserItem,
119+
val error: UserError,
120+
val indexProducer: () -> Int?,
121+
) : RemoveUser
118122
}
119123
}

feature-main/src/main/java/com/hoc/flowmvi/ui/main/MainVM.kt

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,21 @@ class MainVM(
5858
}
5959

6060
private fun Flow<PartialChange>.sendSingleEvent(): Flow<PartialChange> {
61-
return onEach {
62-
val event = when (it) {
63-
is PartialChange.GetUser.Error -> SingleEvent.GetUsersError(it.error)
61+
return onEach { change ->
62+
val event = when (change) {
63+
is PartialChange.GetUser.Error -> SingleEvent.GetUsersError(change.error)
6464
is PartialChange.Refresh.Success -> SingleEvent.Refresh.Success
65-
is PartialChange.Refresh.Failure -> SingleEvent.Refresh.Failure(it.error)
66-
is PartialChange.RemoveUser.Success -> SingleEvent.RemoveUser.Success(it.user)
65+
is PartialChange.Refresh.Failure -> SingleEvent.Refresh.Failure(change.error)
66+
is PartialChange.RemoveUser.Success -> SingleEvent.RemoveUser.Success(change.user)
6767
is PartialChange.RemoveUser.Failure -> SingleEvent.RemoveUser.Failure(
68-
user = it.user,
69-
error = it.error,
68+
user = change.user,
69+
error = change.error,
70+
indexProducer = {
71+
viewState.value
72+
.userItems
73+
.indexOfFirst { it.id == change.user.id }
74+
.takeIf { it != -1 }
75+
}
7076
)
7177
PartialChange.GetUser.Loading -> return@onEach
7278
is PartialChange.GetUser.Data -> return@onEach

feature-main/src/test/java/com/hoc/flowmvi/ui/main/MainVMTest.kt

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.hoc.flowmvi.ui.main
33
import arrow.core.left
44
import arrow.core.right
55
import com.flowmvi.mvi_testing.BaseMviViewModelTest
6+
import com.flowmvi.mvi_testing.mapRight
67
import com.hoc.flowmvi.domain.entity.User
78
import com.hoc.flowmvi.domain.repository.UserError
89
import com.hoc.flowmvi.domain.usecase.GetUsersUseCase
@@ -22,6 +23,8 @@ import kotlinx.coroutines.flow.emptyFlow
2223
import kotlinx.coroutines.flow.flowOf
2324
import kotlinx.coroutines.flow.update
2425
import kotlin.test.Test
26+
import kotlin.test.assertEquals
27+
import kotlin.test.assertIs
2528
import kotlin.time.Duration
2629
import kotlin.time.ExperimentalTime
2730

@@ -78,7 +81,7 @@ class MainVMTest : BaseMviViewModelTest<
7881
error = null,
7982
isRefreshing = false
8083
)
81-
),
84+
).mapRight(),
8285
expectedEvents = emptyList(),
8386
) { verify(exactly = 1) { getUserUseCase() } }
8487

@@ -100,12 +103,12 @@ class MainVMTest : BaseMviViewModelTest<
100103
error = userError,
101104
isRefreshing = false
102105
)
103-
),
106+
).mapRight(),
104107
expectedEvents = listOf(
105108
SingleEvent.GetUsersError(
106109
error = userError,
107110
),
108-
),
111+
).mapRight(),
109112
) { verify(exactly = 1) { getUserUseCase() } }
110113
}
111114

@@ -137,10 +140,10 @@ class MainVMTest : BaseMviViewModelTest<
137140
error = null,
138141
isRefreshing = false
139142
),
140-
),
143+
).mapRight(),
141144
expectedEvents = listOf(
142145
SingleEvent.Refresh.Success
143-
),
146+
).mapRight(),
144147
) {
145148
coVerify(exactly = 1) { getUserUseCase() }
146149
coVerify(exactly = 1) { refreshGetUsersUseCase() }
@@ -177,10 +180,10 @@ class MainVMTest : BaseMviViewModelTest<
177180
error = null,
178181
isRefreshing = false
179182
),
180-
),
183+
).mapRight(),
181184
expectedEvents = listOf(
182185
SingleEvent.Refresh.Failure(userError)
183-
),
186+
).mapRight(),
184187
) {
185188
coVerify(exactly = 1) { getUserUseCase() }
186189
coVerify(exactly = 1) { refreshGetUsersUseCase() }
@@ -195,7 +198,7 @@ class MainVMTest : BaseMviViewModelTest<
195198
vm
196199
},
197200
intents = flowOf(ViewIntent.Refresh),
198-
expectedStates = listOf(ViewState.initial()),
201+
expectedStates = listOf(ViewState.initial()).mapRight(),
199202
expectedEvents = emptyList(),
200203
delayAfterDispatchingIntents = Duration.milliseconds(100),
201204
) { coVerify(exactly = 0) { refreshGetUsersUseCase() } }
@@ -220,10 +223,10 @@ class MainVMTest : BaseMviViewModelTest<
220223
error = userError,
221224
isRefreshing = false,
222225
)
223-
),
226+
).mapRight(),
224227
expectedEvents = listOf(
225228
SingleEvent.GetUsersError(userError),
226-
),
229+
).mapRight(),
227230
delayAfterDispatchingIntents = Duration.milliseconds(100),
228231
) {
229232
coVerify(exactly = 1) { getUserUseCase() }
@@ -239,7 +242,7 @@ class MainVMTest : BaseMviViewModelTest<
239242
vm
240243
},
241244
intents = flowOf(ViewIntent.Retry),
242-
expectedStates = listOf(ViewState.initial()),
245+
expectedStates = listOf(ViewState.initial()).mapRight(),
243246
expectedEvents = emptyList(),
244247
delayAfterDispatchingIntents = Duration.milliseconds(100),
245248
) { coVerify(exactly = 0) { getUserUseCase() } }
@@ -278,10 +281,10 @@ class MainVMTest : BaseMviViewModelTest<
278281
error = null,
279282
isRefreshing = false,
280283
)
281-
),
284+
).mapRight(),
282285
expectedEvents = listOf(
283286
SingleEvent.GetUsersError(userError),
284-
),
287+
).mapRight(),
285288
) { verify(exactly = 2) { getUserUseCase() } }
286289
}
287290

@@ -319,11 +322,11 @@ class MainVMTest : BaseMviViewModelTest<
319322
error = userError2,
320323
isRefreshing = false,
321324
)
322-
),
325+
).mapRight(),
323326
expectedEvents = listOf(
324327
SingleEvent.GetUsersError(userError1),
325328
SingleEvent.GetUsersError(userError2),
326-
),
329+
).mapRight(),
327330
) { verify(exactly = 2) { getUserUseCase() } }
328331
}
329332

@@ -375,11 +378,11 @@ class MainVMTest : BaseMviViewModelTest<
375378
error = null,
376379
isRefreshing = false,
377380
),
378-
),
381+
).mapRight(),
379382
expectedEvents = listOf(
380383
SingleEvent.RemoveUser.Success(item1),
381384
SingleEvent.RemoveUser.Success(item2),
382-
)
385+
).mapRight()
383386
) {
384387
coVerify(exactly = 1) { getUserUseCase() }
385388
coVerifySequence {
@@ -410,9 +413,14 @@ class MainVMTest : BaseMviViewModelTest<
410413
error = null,
411414
isRefreshing = false,
412415
),
413-
),
416+
).mapRight(),
414417
expectedEvents = listOf(
415-
SingleEvent.RemoveUser.Failure(item, userError),
418+
{ event: SingleEvent ->
419+
val removed = assertIs<SingleEvent.RemoveUser.Failure>(event)
420+
assertEquals(item, removed.user)
421+
assertEquals(userError, removed.error)
422+
assertEquals(removed.indexProducer(), 0)
423+
}.left(),
416424
)
417425
) {
418426
coVerify(exactly = 1) { getUserUseCase() }

mvi/mvi-testing/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,7 @@ dependencies {
3838
implementation(mviBase)
3939
implementation(deps.timber)
4040

41+
implementation(deps.arrow.core)
42+
4143
addUnitTest(testImplementation = false)
4244
}

mvi/mvi-testing/src/main/java/com/flowmvi/mvi_testing/BaseMviViewModelTest.kt

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.flowmvi.mvi_testing
22

33
import androidx.annotation.CallSuper
4+
import arrow.core.Either
5+
import arrow.core.right
46
import com.hoc.flowmvi.mvi_base.MviIntent
57
import com.hoc.flowmvi.mvi_base.MviSingleEvent
68
import com.hoc.flowmvi.mvi_base.MviViewModel
@@ -20,7 +22,6 @@ import kotlinx.coroutines.test.runBlockingTest
2022
import kotlinx.coroutines.test.setMain
2123
import kotlin.test.AfterTest
2224
import kotlin.test.BeforeTest
23-
import kotlin.test.assertContentEquals
2425
import kotlin.test.assertEquals
2526
import kotlin.time.Duration
2627
import kotlin.time.ExperimentalTime
@@ -44,8 +45,8 @@ abstract class BaseMviViewModelTest<
4445
protected fun test(
4546
vmProducer: () -> VM,
4647
intents: Flow<I>,
47-
expectedStates: List<S>,
48-
expectedEvents: List<E>,
48+
expectedStates: List<Either<(S) -> Unit, S>>,
49+
expectedEvents: List<Either<(E) -> Unit, E>>,
4950
delayAfterDispatchingIntents: Duration = Duration.ZERO,
5051
logging: Boolean = true,
5152
intentsBeforeCollecting: Flow<I>? = null,
@@ -69,18 +70,34 @@ abstract class BaseMviViewModelTest<
6970
}
7071

7172
assertEquals(expectedStates.size, states.size, "States size")
72-
assertContentEquals(
73-
expectedStates,
74-
states,
75-
"States content"
76-
)
73+
expectedStates.withIndex().zip(states).forEach { (indexedValue, state) ->
74+
val (index, exp) = indexedValue
75+
exp.fold(
76+
ifRight = {
77+
assertEquals(
78+
expected = it,
79+
actual = state,
80+
message = "[State index=$index]"
81+
)
82+
},
83+
ifLeft = { it(state) }
84+
)
85+
}
7786

7887
assertEquals(expectedEvents.size, events.size, "Events size")
79-
assertContentEquals(
80-
expectedEvents,
81-
events,
82-
"Evens content",
83-
)
88+
expectedEvents.withIndex().zip(events).forEach { (indexedValue, event) ->
89+
val (index, exp) = indexedValue
90+
exp.fold(
91+
ifRight = {
92+
assertEquals(
93+
expected = it,
94+
actual = event,
95+
message = "[Event index=$index]"
96+
)
97+
},
98+
ifLeft = { it(event) }
99+
)
100+
}
84101

85102
otherAssertions?.invoke()
86103
stateJob.cancel()
@@ -95,3 +112,5 @@ abstract class BaseMviViewModelTest<
95112
clearAllMocks()
96113
}
97114
}
115+
116+
fun <T> Iterable<T>.mapRight(): List<Either<(T) -> Unit, T>> = map { it.right() }

0 commit comments

Comments
 (0)