Skip to content

Commit

Permalink
refactor/#4 : Group Feature mvi base ViewModel 선언
Browse files Browse the repository at this point in the history
- Intent 전달 방식 수정
- reducer 함수 강압적인 사용, intent 강제 활용
- reducer 함수 호출
  • Loading branch information
TaewoongR committed Jan 16, 2025
1 parent 7ef1197 commit 07e8126
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 211 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,23 @@ fun GroupCreationScreen(
viewModel: GroupCreationViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val uiState by viewModel.state.collectAsStateWithLifecycle()
val effect = rememberFlowWithLifecycle(
flow = viewModel.sideEffect,
flow = viewModel.effect,
initialValue = GroupCreationSideEffect.Idle,
).value

BackHandler {
if (uiState.isSelectingGroupImage) {
viewModel.onIntent(GroupCreationIntent.OnBackToGroupCreation)
viewModel.sendIntent(GroupCreationIntent.OnBackToGroupCreation)
} else {
viewModel.onIntent(GroupCreationIntent.OnBackClick)
viewModel.sendIntent(GroupCreationIntent.OnBackClick)
}
}

LaunchedEffect(Unit) {
if (!uiState.isInitializing) {
viewModel.onIntent(GroupCreationIntent.Initialize)
viewModel.sendIntent(GroupCreationIntent.Initialize)
}
}

Expand All @@ -98,15 +98,15 @@ fun GroupCreationScreen(
MapisodePhotoPicker(
numOfPhoto = 1,
onPhotoSelected = { photoList ->
viewModel.onIntent(
viewModel.sendIntent(
GroupCreationIntent.OnGroupImageSelect(
photoList.first().uri,
),
)
},
onPermissionDenied = { viewModel.onIntent(GroupCreationIntent.OnBackToGroupCreation) },
onPermissionDenied = { viewModel.sendIntent(GroupCreationIntent.OnBackToGroupCreation) },
onBackPressed = {
viewModel.onIntent(GroupCreationIntent.OnBackToGroupCreation)
viewModel.sendIntent(GroupCreationIntent.OnBackToGroupCreation)
},
isCameraNeeded = false,
)
Expand All @@ -115,7 +115,7 @@ fun GroupCreationScreen(
imageUrl = uiState.group.imageUrl,
onBackClick = onBackClick,
onGroupEditClick = { title, content, imageUrl ->
viewModel.onIntent(
viewModel.sendIntent(
GroupCreationIntent.OnGroupCreationClick(
title = title,
content = content,
Expand All @@ -124,7 +124,7 @@ fun GroupCreationScreen(
)
},
onPhotoPickerClick = {
viewModel.onIntent(GroupCreationIntent.OnPhotoPickerClick)
viewModel.sendIntent(GroupCreationIntent.OnPhotoPickerClick)
},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ fun GroupDetailScreen(
viewModel: GroupDetailViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
val uiState = viewModel.state.collectAsStateWithLifecycle()
val effect = rememberFlowWithLifecycle(
flow = viewModel.sideEffect,
flow = viewModel.effect,
initialValue = GroupDetailSideEffect.Idle,
).value

Expand All @@ -104,13 +104,13 @@ fun GroupDetailScreen(
MapisodeDialog(
onResultRequest = { isPositive ->
if (isPositive) {
viewModel.onIntent(GroupDetailIntent.OnGroupOutConfirm)
viewModel.sendIntent(GroupDetailIntent.OnGroupOutConfirm)
} else {
viewModel.onIntent(GroupDetailIntent.OnGroupOutCancel)
viewModel.sendIntent(GroupDetailIntent.OnGroupOutCancel)
}
},
onDismissRequest = {
viewModel.onIntent(GroupDetailIntent.OnGroupOutCancel)
viewModel.sendIntent(GroupDetailIntent.OnGroupOutCancel)
},
titleText = stringResource(S.string.dialog_group_out_title),
contentText = stringResource(S.string.dialog_group_out_message),
Expand All @@ -122,17 +122,17 @@ fun GroupDetailScreen(
LaunchedEffect(uiState.value) {
with(uiState.value) {
if (isGroupIdCaching) {
viewModel.onIntent(GroupDetailIntent.InitializeGroupDetail(detail.groupId))
viewModel.sendIntent(GroupDetailIntent.InitializeGroupDetail(detail.groupId))
}
if (isGroupLoading) {
viewModel.onIntent(GroupDetailIntent.TryGetGroup(detail.groupId))
viewModel.sendIntent(GroupDetailIntent.TryGetGroup(detail.groupId))
}
if (!isGroupIdCaching && !isGroupLoading && membersInfo.isEmpty()) {
viewModel.onIntent(GroupDetailIntent.TryGetUserInfo)
viewModel.sendIntent(GroupDetailIntent.TryGetUserInfo)
}
// 최초 진입 시 보이지 않는 탭, 후순위 로딩
if (episodes.isEmpty() && membersInfo.isNotEmpty()) {
viewModel.onIntent(GroupDetailIntent.TryGetGroupEpisodes)
viewModel.sendIntent(GroupDetailIntent.TryGetGroupEpisodes)
}
}
}
Expand Down Expand Up @@ -175,19 +175,19 @@ fun GroupDetailScreen(
GroupDetailContent(
uiState = uiState.value,
onBackClick = {
viewModel.onIntent(GroupDetailIntent.OnBackClick)
viewModel.sendIntent(GroupDetailIntent.OnBackClick)
},
onEditClick = {
viewModel.onIntent(GroupDetailIntent.OnEditClick)
viewModel.sendIntent(GroupDetailIntent.OnEditClick)
},
onIssueCodeClick = {
viewModel.onIntent(GroupDetailIntent.OnIssueCodeClick)
viewModel.sendIntent(GroupDetailIntent.OnIssueCodeClick)
},
onGroupOutClick = {
viewModel.onIntent(GroupDetailIntent.OnGroupOutClick)
viewModel.sendIntent(GroupDetailIntent.OnGroupOutClick)
},
onEpisodeClick = { episodeId ->
viewModel.onIntent(GroupDetailIntent.OnEpisodeClick(episodeId))
viewModel.sendIntent(GroupDetailIntent.OnEpisodeClick(episodeId))
},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,23 @@ fun GroupEditScreen(
viewModel: GroupEditViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val uiState by viewModel.state.collectAsStateWithLifecycle()
val effect = rememberFlowWithLifecycle(
flow = viewModel.sideEffect,
flow = viewModel.effect,
initialValue = GroupEditSideEffect.Idle,
).value

BackHandler {
if (uiState.isSelectingGroupImage) {
viewModel.onIntent(GroupEditIntent.OnBackToGroupCreation)
viewModel.sendIntent(GroupEditIntent.OnBackToGroupCreation)
} else {
viewModel.onIntent(GroupEditIntent.OnBackClick)
viewModel.sendIntent(GroupEditIntent.OnBackClick)
}
}

LaunchedEffect(Unit) {
if (!uiState.isInitializing) {
viewModel.onIntent(GroupEditIntent.Initialize(edit.groupId))
viewModel.sendIntent(GroupEditIntent.Initialize(edit.groupId))
}
}

Expand All @@ -102,15 +102,15 @@ fun GroupEditScreen(
MapisodePhotoPicker(
numOfPhoto = 1,
onPhotoSelected = { photoList ->
viewModel.onIntent(
viewModel.sendIntent(
GroupEditIntent.OnGroupImageSelect(
photoList.first().uri,
),
)
},
onPermissionDenied = { viewModel.onIntent(GroupEditIntent.DenyPhotoPermission) },
onPermissionDenied = { viewModel.sendIntent(GroupEditIntent.DenyPhotoPermission) },
onBackPressed = {
viewModel.onIntent(GroupEditIntent.OnBackToGroupCreation)
viewModel.sendIntent(GroupEditIntent.OnBackToGroupCreation)
},
isCameraNeeded = false,
)
Expand All @@ -119,7 +119,7 @@ fun GroupEditScreen(
uiState = uiState,
onBackClick = onBackClick,
onGroupEditClick = { title, content, imageUrl ->
viewModel.onIntent(
viewModel.sendIntent(
GroupEditIntent.OnGroupEditClick(
title = title,
content = content,
Expand All @@ -128,7 +128,7 @@ fun GroupEditScreen(
)
},
onPhotoPickerClick = {
viewModel.onIntent(GroupEditIntent.OnPhotoPickerClick)
viewModel.sendIntent(GroupEditIntent.OnPhotoPickerClick)
},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ fun GroupJoinScreen(
viewModel: GroupJoinViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val uiState by viewModel.state.collectAsStateWithLifecycle()
val effect = rememberFlowWithLifecycle(
flow = viewModel.sideEffect,
flow = viewModel.effect,
initialValue = GroupJoinSideEffect.Idle,
).value

Expand All @@ -81,13 +81,13 @@ fun GroupJoinScreen(
GroupJoinContent(
uiState = uiState,
onBackClick = {
viewModel.onIntent(intent = GroupJoinIntent.OnBackClick)
viewModel.sendIntent(intent = GroupJoinIntent.OnBackClick)
},
onGetGroup = { joinCodeText ->
viewModel.onIntent(intent = GroupJoinIntent.TryGetGroup(joinCodeText))
viewModel.sendIntent(intent = GroupJoinIntent.TryGetGroup(joinCodeText))
},
onJoinGroup = {
viewModel.onIntent(intent = GroupJoinIntent.OnJoinClick)
viewModel.sendIntent(intent = GroupJoinIntent.OnJoinClick)
},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import com.boostcamp.mapisode.mygroup.sideeffect.GroupSideEffect
import com.boostcamp.mapisode.mygroup.sideeffect.rememberFlowWithLifecycle
import com.boostcamp.mapisode.mygroup.state.GroupState
import com.boostcamp.mapisode.mygroup.viewmodel.GroupViewModel
import timber.log.Timber
import com.boostcamp.mapisode.mygroup.R as S

@Composable
Expand All @@ -47,9 +48,9 @@ internal fun MainGroupRoute(
viewModel: GroupViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
val uiState = viewModel.state.collectAsStateWithLifecycle()
val effect = rememberFlowWithLifecycle(
flow = viewModel.sideEffect,
flow = viewModel.effect,
initialValue = GroupSideEffect.Idle,
).value

Expand All @@ -75,21 +76,19 @@ internal fun MainGroupRoute(
}
}

LaunchedEffect(uiState.value) {
if (uiState.value.isInitializing) {
viewModel.onIntent(GroupIntent.LoadGroups)
}
LaunchedEffect(Unit) {
viewModel.sendIntent(GroupIntent.LoadGroups)
}

GroupScreen(
onGroupJoinClick = {
viewModel.onIntent(GroupIntent.OnJoinClick)
viewModel.sendIntent(GroupIntent.OnJoinClick)
},
onGroupDetailClick = { groupId ->
viewModel.onIntent(GroupIntent.OnGroupDetailClick(groupId))
viewModel.sendIntent(GroupIntent.OnGroupDetailClick(groupId))
},
onGroupCreationClick = {
viewModel.onIntent(GroupIntent.OnGroupCreateClick)
viewModel.sendIntent(GroupIntent.OnGroupCreateClick)
},
uiState = uiState,
)
Expand All @@ -107,14 +106,14 @@ private fun <T> GroupScreen(

MapisodeScaffold(
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures(
onPress = {
focusManager.clearFocus()
},
)
},
.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures(
onPress = {
focusManager.clearFocus()
},
)
},
isStatusBarPaddingExist = true,
topBar = {
TopAppBar(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.boostcamp.mapisode.mygroup.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.boostcamp.mapisode.ui.base.SideEffect
import com.boostcamp.mapisode.ui.base.UiIntent
import com.boostcamp.mapisode.ui.base.UiState
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

abstract class GroupBaseViewModel<UI_INTENT: UiIntent, UI_STATE: UiState, UI_EFFECT: SideEffect >(
initialState: UI_STATE
): ViewModel() {
private val _state = MutableStateFlow(initialState)
val state = _state.asStateFlow()

private val _intent = MutableSharedFlow<UI_INTENT>()

private val _effect = Channel<SideEffect>()
val effect = _effect.receiveAsFlow()

protected val currentState: UI_STATE
get() = _state.value

/**
* initialize the reducer with the intent argument
*/
init {
viewModelScope.launch {
reducer(_intent.asSharedFlow())
}
}

/**
* Use intent to update the state and send the effect
*/
abstract suspend fun reducer(intent: SharedFlow<UI_INTENT>)

fun sendIntent(intent: UI_INTENT) {
viewModelScope.launch { _intent.emit(intent) }
}

protected fun sendState(reduce: UI_STATE.() -> UI_STATE) {
_state.update { currentState.reduce() }
}

fun sendEffect(builder: () -> UI_EFFECT) {
viewModelScope.launch { _effect.send(builder()) }
}
}
Loading

0 comments on commit 07e8126

Please sign in to comment.