Vivek Modi
04/04/2025, 9:21 PMVivek Modi
04/04/2025, 9:21 PMVivek Modi
04/04/2025, 9:21 PMsealed interface ProfileUiEvent {
data object OnChangePasswordClick : ProfileUiEvent
data object DeleteAccountClick : ProfileUiEvent
data object LogoutClick : ProfileUiEvent
data class ShowError(val message: String?) : ProfileUiEvent
}
ProfileViewModel
class ProfileViewModel(
private val userInfoApi: UserInfoApi,
private val logoutApi: LogoutApi,
private val tokenDataSource: TokenDataSource,
private val userSession: UserSessionDataSource,
) : ViewModel() {
private val eventChannel = Channel<ProfileUiEvent>()
val events = eventChannel.receiveAsFlow()
private val loadingState = MutableStateFlow(true)
val profileUiState = combine(
flow {
emit(userInfoApi.getUserInfo()).also {
loadingState.update { false }
}
},
loadingState,
) { userInfo, loadingState ->
userInfo.fold(
onSuccess = {
ProfileUiState(userInfo = it, isLoading = loadingState)
},
onFailure = {
ProfileUiState(isLoading = loadingState)
eventChannel.send(ProfileUiEvent.ShowError(it.message))
}
)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = ProfileUiState(isLoading = true)
)
fun onAction(action: ProfileUiEvent) {
when (action) {
ProfileUiEvent.LogoutClick -> {
logoutUser()
}
else -> {}
}
}
private fun logoutUser() {
viewModelScope.launch {
loadingState.update { true }
val result = logoutApi.logout()
loadingState.update { false }
result.fold(
onSuccess = {
onLogoutSuccess()
},
onFailure = {
eventChannel.send(ProfileUiEvent.ShowError(it.message))
}
)
}
}
private suspend fun onLogoutSuccess() {
tokenDataSource.clearTokens()
userSession.logout("Logout")
}
}
UserInfoApi
interface UserInfoApi {
suspend fun getUserInfo(): Result<UserInfo>
}
LogoutApi
interface LogoutApi {
suspend fun logout(): Result<Unit>
}
ProfileScreenRoute
@Composable
fun ProfileScreenRoute(
onBackPress: () -> Unit,
onNavigateToChangePassword: () -> Unit,
onNavigateToDeleteAccount: () -> Unit,
viewModel: ProfileViewModel = koinViewModel(),
) {
val context = LocalContext.current
val currentOnNavigateToChangePassword by rememberUpdatedState(onNavigateToChangePassword)
val currentOnNavigateToDeleteAccount by rememberUpdatedState(onNavigateToDeleteAccount)
val profileUiState by viewModel.profileUiState.collectAsStateWithLifecycle()
ObserveAsEvent(viewModel.events) { event ->
when (event) {
is ProfileUiEvent.OnChangePasswordClick -> {
currentOnNavigateToChangePassword()
}
is ProfileUiEvent.DeleteAccountClick -> {
currentOnNavigateToDeleteAccount()
}
is ProfileUiEvent.ShowError -> {
Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
}
else -> {}
}
}
BackHandler(onBack = onBackPress)
ProfileScreen(
uiState = profileUiState,
onAction = viewModel::onAction
)
}
Pablichjenkov
04/04/2025, 9:32 PMPablichjenkov
04/04/2025, 9:41 PMpreviousNavBackstackEntry.savedState
API for this but using the domain/data layer scales better.Pablichjenkov
04/04/2025, 9:41 PMPablichjenkov
04/04/2025, 9:43 PMVivek Modi
04/05/2025, 10:43 AM@Composable
fun AppNavHost(
startDestination: Any,
navHostController: NavHostController = rememberNavController()
) {
val currentActivity = LocalActivity.current
AppAppScaffold(
bottomBar = {
HomeTopLevelNavigation(navHostController)
}
) { contentPadding ->
CompositionLocalProvider(LocalScaffoldPadding provides ScaffoldPadding(contentPadding)) {
NavHost(
navController = navHostController,
startDestination = startDestination
) {
// more routes here composable<HomeGraphNavigation.ProfileScreen> {
ProfileScreenRoute(
onBackPress = {
currentActivity?.finish()
},
onNavigateToChangePassword = navHostController::navigateToChangePassword,
onNavigateToDeleteAccount = navHostController::navigateToDeleteAccount,
)
}
}
}
}
}
Vivek Modi
04/05/2025, 10:44 AMfun NavController.navigateToChangePassword() {
navigate(AuthNavigation.ChangePasswordNavigation)
}
Vivek Modi
04/05/2025, 10:45 AMfun NavController.navigateToDeleteAccount() {
navigate(AuthNavigation.DeleteAccountNavigation)
}
Pablichjenkov
04/05/2025, 9:20 PMVivek Modi
04/06/2025, 7:15 AMPablichjenkov
04/08/2025, 3:43 AMclass ProfileNavigationManager(navController, dataManagers, ...)
Then in your navGraph builder function , just pass an instance of it, anytime you want to navigate, instead of calling directly navController.navigate(...) better delegate the where to go next
navigation logic to ProfileNavigationManager class. It looks a bit more organized than having this logic in extension functions.
And you have the needed DataManagers to determine what is the next screen all in one place where you can mock all them