-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ISSUE-011] BaseViewModel 및 샘플 데이터 생성 #27
Changes from 2 commits
9039bd3
e16fc3b
d5e4061
66079bf
301a42c
b24f39b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package com.yapp.growth.base | ||
|
||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import kotlinx.coroutines.channels.Channel | ||
import kotlinx.coroutines.flow.* | ||
import kotlinx.coroutines.launch | ||
|
||
abstract class BaseViewModel<S : ViewState, A : ViewSideEffect, E : ViewEvent>( | ||
initialState: S | ||
) : ViewModel() { | ||
|
||
abstract fun handleEvents(event: E) | ||
|
||
private val _viewState: MutableStateFlow<S> = MutableStateFlow<S>(initialState) | ||
val viewState = _viewState.asStateFlow() | ||
|
||
private val currentState: S | ||
get() = _viewState.value | ||
|
||
private val _effect: Channel<A> = Channel() | ||
val effect = _effect.receiveAsFlow() | ||
|
||
private val _event: MutableSharedFlow<E> = MutableSharedFlow() | ||
|
||
init { | ||
subscribeToEvents() | ||
} | ||
|
||
private fun subscribeToEvents() { | ||
viewModelScope.launch { | ||
_event.collect { | ||
handleEvents(it) | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. setEvent 에서 바로 handleEvents(event) 를 호출해도 되지 않나요? 한번 더 구독해서 스트림으로 만든 이유가 궁금합니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 헉.. 맞네요.. 그 방법이 더 좋은 것 같아요..! |
||
|
||
protected fun setState(reducer: S.() -> S) { | ||
val newState = currentState.reducer() | ||
_viewState.value = newState | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 개념도 setXXX 보다는 updateXXX에 가까운 것 같습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
protected fun setEffect(builder: () -> A) { | ||
val effectValue = builder() | ||
viewModelScope.launch { _effect.send(effectValue) } | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이펙트를 새로 set 하는 개념이 아닌 emit 하는 개념이라서 setXXX 보다는 그대로 사용하면 어떨까요? effect { ... } There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 확실히 sendEffect { ... } 로 고치는 것도 괜찮아보이네욥! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. effect가 빌더 형태의 패턴을 받는 장점이 있을까요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 맞습니다... 다만 |
||
|
||
open fun setEvent(event : E) { | ||
viewModelScope.launch { _event.emit(event) } | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. open으로 만든 이유가 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 맞습니다. 재정의 방지를 위해 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package com.yapp.growth.base | ||
|
||
interface ViewEvent { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package com.yapp.growth.base | ||
|
||
interface ViewSideEffect { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package com.yapp.growth.base | ||
|
||
interface ViewState { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.yapp.growth.ui.sample | ||
|
||
import com.yapp.growth.base.ViewEvent | ||
import com.yapp.growth.base.ViewSideEffect | ||
import com.yapp.growth.base.ViewState | ||
|
||
class SampleContract { | ||
|
||
data class SampleViewState( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저희 View, State, Event, Effect의 네이밍을 어떻게 가져갈 것인지 정해야 할 것 같은데 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 베이스 짜면서 이렇게 생각했어요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하 올려주신 Sample 보고 옮겨 적은 거였는데,, 제가 잘못 옮겨적었네요.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋아요! 그럼 다른건 그대로 유지하되 |
||
val isLoading: Boolean = false | ||
) : ViewState | ||
|
||
sealed class SampleViewSideEffect : ViewSideEffect { | ||
object NavigateToAnyScreen : SampleViewSideEffect() | ||
data class ShowToast(val msg: String) : SampleViewSideEffect() | ||
} | ||
|
||
sealed class SampleViewEvent : ViewEvent { | ||
object OnAnyButtonClicked : SampleViewEvent() | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.yapp.growth.ui.sample | ||
|
||
import com.yapp.growth.base.BaseViewModel | ||
import com.yapp.growth.domain.usecase.UseCase | ||
import com.yapp.growth.ui.sample.SampleContract.* | ||
|
||
class SampleViewModel( | ||
private val useCase: UseCase | ||
) : BaseViewModel<SampleViewState, SampleViewSideEffect, SampleViewEvent>( | ||
SampleViewState() | ||
) { | ||
|
||
fun anyFunction() { | ||
// anything do . . . | ||
setEffect { SampleViewSideEffect.NavigateToAnyScreen } | ||
setState { copy(isLoading = false) } | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. anyFuction()은 private이 되어야 할 것 같네요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵 현재 코드에선 맞습니다 ㅠ! |
||
|
||
override fun handleEvents(event: SampleViewEvent) { | ||
when (event) { | ||
is SampleViewEvent.OnAnyButtonClicked -> { | ||
setState { copy(isLoading = true) } | ||
anyFunction() | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SharedFlow 대신 Channel을 사용한 장점이 있나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
effect 의 경우 어떤 이벤트가 발생했을 때 한번만 실행되어야 하는데, 모든 구독자에게 데이터를 방출하는
SharedFlow
를 사용하는 것은 옳지 않다고 생각합니다. 반면Channel
의 경우 한 구독자에게 한번씩만 데이터를 방출하기 때문에, 사용하기 적절하다고 판단했습니다.SharedFlow
의 경우 구독자가 나타나지 않아도 데이터가 방출되는 반면,Channel
의 경우 구독자가 없을 경우 방출되지 않는데 이 요소 또한 중요하다고 생각합니다.이 두 이유를 근거로 제가 생각한 하나의 예시를 들어보겠습니다.
라고 판단했는데, 부족한 점이나 틀린 점 지적해주시면 감사하겠습니다 🤣