Skip to content

Commit 29f36d0

Browse files
committed
feat(add-new-user): refactor viewmodel
1 parent 600cf55 commit 29f36d0

File tree

4 files changed

+146
-123
lines changed

4 files changed

+146
-123
lines changed

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ sealed interface ViewIntent : MviIntent {
7070
internal sealed interface PartialStateChange {
7171
fun reduce(viewState: ViewState): ViewState
7272

73-
data class ErrorsChanged(val errors: PersistentSet<UserValidationError>) : PartialStateChange {
73+
data class Errors(val errors: PersistentSet<UserValidationError>) : PartialStateChange {
7474
override fun reduce(viewState: ViewState) =
7575
if (viewState.errors == errors) viewState
7676
else viewState.copy(errors = errors)
@@ -104,7 +104,7 @@ internal sealed interface PartialStateChange {
104104
}
105105
}
106106

107-
sealed class FormValueChange : PartialStateChange {
107+
sealed class FormValue : PartialStateChange {
108108
override fun reduce(viewState: ViewState): ViewState {
109109
return when (this) {
110110
is EmailChanged -> {
@@ -122,9 +122,9 @@ internal sealed interface PartialStateChange {
122122
}
123123
}
124124

125-
data class EmailChanged(val email: String?) : FormValueChange()
126-
data class FirstNameChanged(val firstName: String?) : FormValueChange()
127-
data class LastNameChanged(val lastName: String?) : FormValueChange()
125+
data class EmailChanged(val email: String?) : FormValue()
126+
data class FirstNameChanged(val firstName: String?) : FormValue()
127+
data class LastNameChanged(val lastName: String?) : FormValue()
128128
}
129129
}
130130

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

Lines changed: 48 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package com.hoc.flowmvi.ui.add
22

33
import androidx.lifecycle.SavedStateHandle
44
import androidx.lifecycle.viewModelScope
5+
import arrow.core.ValidatedNel
56
import arrow.core.orNull
67
import com.hoc.flowmvi.domain.model.User
8+
import com.hoc.flowmvi.domain.model.UserValidationError
79
import com.hoc.flowmvi.domain.usecase.AddUserUseCase
810
import com.hoc.flowmvi.mvi_base.AbstractMviViewModel
911
import com.hoc081098.flowext.flatMapFirst
@@ -32,6 +34,8 @@ import kotlinx.coroutines.flow.take
3234
import timber.log.Timber
3335
import javax.inject.Inject
3436

37+
private typealias UserFormStateFlow = StateFlow<ValidatedNel<UserValidationError, User>?>
38+
3539
@HiltViewModel
3640
@ExperimentalCoroutinesApi
3741
class AddVM @Inject constructor(
@@ -49,7 +53,7 @@ class AddVM @Inject constructor(
4953
Timber.tag(logTag).d("[ADD_VM] initialVS: $initialVS")
5054

5155
viewState = intentFlow
52-
.toPartialStateChangesFlow(initialVS)
56+
.toPartialStateChangeFlow(initialVS)
5357
.log("PartialStateChange")
5458
.sendSingleEvent()
5559
.scan(initialVS) { state, change -> change.reduce(state) }
@@ -61,7 +65,7 @@ class AddVM @Inject constructor(
6165
private fun Flow<PartialStateChange>.sendSingleEvent(): Flow<PartialStateChange> {
6266
return onEach { change ->
6367
val event = when (change) {
64-
is PartialStateChange.ErrorsChanged -> return@onEach
68+
is PartialStateChange.Errors -> return@onEach
6569
PartialStateChange.AddUser.Loading -> return@onEach
6670
is PartialStateChange.AddUser.AddUserSuccess -> SingleEvent.AddUserSuccess(change.user)
6771
is PartialStateChange.AddUser.AddUserFailure -> SingleEvent.AddUserFailure(
@@ -71,15 +75,15 @@ class AddVM @Inject constructor(
7175
PartialStateChange.FirstChange.EmailChangedFirstTime -> return@onEach
7276
PartialStateChange.FirstChange.FirstNameChangedFirstTime -> return@onEach
7377
PartialStateChange.FirstChange.LastNameChangedFirstTime -> return@onEach
74-
is PartialStateChange.FormValueChange.EmailChanged -> return@onEach
75-
is PartialStateChange.FormValueChange.FirstNameChanged -> return@onEach
76-
is PartialStateChange.FormValueChange.LastNameChanged -> return@onEach
78+
is PartialStateChange.FormValue.EmailChanged -> return@onEach
79+
is PartialStateChange.FormValue.FirstNameChanged -> return@onEach
80+
is PartialStateChange.FormValue.LastNameChanged -> return@onEach
7781
}
7882
sendEvent(event)
7983
}
8084
}
8185

82-
private fun SharedFlow<ViewIntent>.toPartialStateChangesFlow(initialVS: ViewState): Flow<PartialStateChange> {
86+
private fun SharedFlow<ViewIntent>.toPartialStateChangeFlow(initialVS: ViewState): Flow<PartialStateChange> {
8387
val emailFlow = filterIsInstance<ViewIntent.EmailChanged>()
8488
.map { it.email }
8589
.startWith(initialVS.email)
@@ -112,8 +116,41 @@ class AddVM @Inject constructor(
112116
)
113117
}.stateWithInitialNullWhileSubscribed()
114118

115-
val addUserChanges = filterIsInstance<ViewIntent.Submit>()
116-
.withLatestFrom(userFormFlow) { _, userForm -> userForm }
119+
val formValuesChangeFlow = merge(
120+
emailFlow.map { PartialStateChange.FormValue.EmailChanged(it) },
121+
firstNameFlow.map { PartialStateChange.FormValue.FirstNameChanged(it) },
122+
lastNameFlow.map { PartialStateChange.FormValue.LastNameChanged(it) },
123+
)
124+
125+
return merge(
126+
// form values change
127+
formValuesChangeFlow,
128+
// first change
129+
toFirstChangeFlow(),
130+
// errors change
131+
userFormFlow.toErrorsChangeFlow(),
132+
// add user change
133+
filterIsInstance<ViewIntent.Submit>()
134+
.toAddUserChangeFlow(userFormFlow),
135+
)
136+
}
137+
138+
//region Processors
139+
private fun SharedFlow<ViewIntent>.toFirstChangeFlow(): Flow<PartialStateChange.FirstChange> =
140+
merge(
141+
filterIsInstance<ViewIntent.EmailChanged>()
142+
.take(1)
143+
.mapTo(PartialStateChange.FirstChange.EmailChangedFirstTime),
144+
filterIsInstance<ViewIntent.FirstNameChanged>()
145+
.take(1)
146+
.mapTo(PartialStateChange.FirstChange.FirstNameChangedFirstTime),
147+
filterIsInstance<ViewIntent.LastNameChanged>()
148+
.take(1)
149+
.mapTo(PartialStateChange.FirstChange.LastNameChangedFirstTime)
150+
)
151+
152+
private fun Flow<ViewIntent.Submit>.toAddUserChangeFlow(userFormFlow: UserFormStateFlow): Flow<PartialStateChange.AddUser> =
153+
withLatestFrom(userFormFlow) { _, userForm -> userForm }
117154
.mapNotNull { it?.orNull() }
118155
.flatMapFirst { user ->
119156
flowFromSuspend { addUser(user) }
@@ -126,44 +163,15 @@ class AddVM @Inject constructor(
126163
.startWith(PartialStateChange.AddUser.Loading)
127164
}
128165

129-
val firstChanges = firstChangesFlow()
130-
131-
val formValuesChanges = merge(
132-
emailFlow.map { PartialStateChange.FormValueChange.EmailChanged(it) },
133-
firstNameFlow.map { PartialStateChange.FormValueChange.FirstNameChanged(it) },
134-
lastNameFlow.map { PartialStateChange.FormValueChange.LastNameChanged(it) },
135-
)
136-
137-
val errorsChanges = userFormFlow.map { validated ->
138-
PartialStateChange.ErrorsChanged(
166+
private fun UserFormStateFlow.toErrorsChangeFlow(): Flow<PartialStateChange.Errors> =
167+
map { validated ->
168+
PartialStateChange.Errors(
139169
validated?.fold(
140170
{ it.toPersistentHashSet() },
141171
{ persistentHashSetOf() }
142172
) ?: persistentHashSetOf()
143173
)
144174
}
145-
146-
return merge(
147-
formValuesChanges,
148-
errorsChanges,
149-
addUserChanges,
150-
firstChanges,
151-
)
152-
}
153-
154-
//region Intent processors
155-
private fun SharedFlow<ViewIntent>.firstChangesFlow() =
156-
merge(
157-
filterIsInstance<ViewIntent.EmailChanged>()
158-
.take(1)
159-
.mapTo(PartialStateChange.FirstChange.EmailChangedFirstTime),
160-
filterIsInstance<ViewIntent.FirstNameChanged>()
161-
.take(1)
162-
.mapTo(PartialStateChange.FirstChange.FirstNameChangedFirstTime),
163-
filterIsInstance<ViewIntent.LastNameChanged>()
164-
.take(1)
165-
.mapTo(PartialStateChange.FirstChange.LastNameChangedFirstTime)
166-
)
167175
//endregion
168176

169177
private companion object {

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

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -65,39 +65,39 @@ data class ViewState(
6565
}
6666
}
6767

68-
internal sealed interface PartialChange {
69-
fun reduce(vs: ViewState): ViewState
68+
internal sealed interface PartialStateChange {
69+
fun reduce(viewState: ViewState): ViewState
7070

71-
sealed class GetUser : PartialChange {
72-
override fun reduce(vs: ViewState): ViewState {
71+
sealed class Users : PartialStateChange {
72+
override fun reduce(viewState: ViewState): ViewState {
7373
return when (this) {
74-
Loading -> vs.copy(
74+
Loading -> viewState.copy(
7575
isLoading = true,
7676
error = null
7777
)
78-
is Data -> vs.copy(
78+
is Data -> viewState.copy(
7979
isLoading = false,
8080
error = null,
8181
userItems = users.toPersistentList()
8282
)
83-
is Error -> vs.copy(
83+
is Error -> viewState.copy(
8484
isLoading = false,
8585
error = error
8686
)
8787
}
8888
}
8989

90-
object Loading : GetUser()
91-
data class Data(val users: List<UserItem>) : GetUser()
92-
data class Error(val error: UserError) : GetUser()
90+
object Loading : Users()
91+
data class Data(val users: List<UserItem>) : Users()
92+
data class Error(val error: UserError) : Users()
9393
}
9494

95-
sealed class Refresh : PartialChange {
96-
override fun reduce(vs: ViewState): ViewState {
95+
sealed class Refresh : PartialStateChange {
96+
override fun reduce(viewState: ViewState): ViewState {
9797
return when (this) {
98-
is Success -> vs.copy(isRefreshing = false)
99-
is Failure -> vs.copy(isRefreshing = false)
100-
Loading -> vs.copy(isRefreshing = true)
98+
is Success -> viewState.copy(isRefreshing = false)
99+
is Failure -> viewState.copy(isRefreshing = false)
100+
Loading -> viewState.copy(isRefreshing = true)
101101
}
102102
}
103103

@@ -106,15 +106,15 @@ internal sealed interface PartialChange {
106106
data class Failure(val error: UserError) : Refresh()
107107
}
108108

109-
sealed class RemoveUser : PartialChange {
109+
sealed class RemoveUser : PartialStateChange {
110110
data class Loading(val user: UserItem) : RemoveUser()
111111
data class Success(val user: UserItem) : RemoveUser()
112112
data class Failure(val user: UserItem, val error: Throwable) : RemoveUser()
113113

114-
override fun reduce(vs: ViewState) = when (this) {
114+
override fun reduce(viewState: ViewState) = when (this) {
115115
is Failure -> {
116-
vs.copy(
117-
userItems = vs.userItems.mutate { userItems ->
116+
viewState.copy(
117+
userItems = viewState.userItems.mutate { userItems ->
118118
userItems.forEachIndexed { index, userItem ->
119119
if (userItem.id == user.id) {
120120
userItems[index] = userItem.copy(isDeleting = false)
@@ -124,8 +124,8 @@ internal sealed interface PartialChange {
124124
}
125125
)
126126
}
127-
is Loading -> vs.copy(
128-
userItems = vs.userItems.mutate { userItems ->
127+
is Loading -> viewState.copy(
128+
userItems = viewState.userItems.mutate { userItems ->
129129
userItems.forEachIndexed { index, userItem ->
130130
if (userItem.id == user.id) {
131131
userItems[index] = userItem.copy(isDeleting = true)
@@ -134,7 +134,7 @@ internal sealed interface PartialChange {
134134
}
135135
}
136136
)
137-
is Success -> vs
137+
is Success -> viewState
138138
}
139139
}
140140
}

0 commit comments

Comments
 (0)