Skip to content

How to get the value in ViewModel by SavedStateHandle when back from a composable? #2315

@Lxiaoqiang

Description

@Lxiaoqiang

Describe the bug
I have two compose function : SendGiftScreen and SelectUserScreen
I navigate from SendGiftScreen to SelectUserScreen and i want to get the value in SendGiftScreenViewModel by savedStateHandle, but i can't get the value

how i should do?

To Reproduce

Koin module and version:

koinVersion = "4.1.1"
navigationCompose = "2.9.5"
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koinVersion"}
koin-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koinVersion"}
koin-compose-navigation = { module = "io.insert-koin:koin-androidx-compose-navigation", version.ref = "koinVersion"}
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koinVersion"}
navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }

Snippet or Sample project to help reproduce

  1. The viewmodel inject
viewModel { SendGiftScreenViewModel(get()) }
   viewModel { SelectUserScreenViewModel() }
  1. SelectUserScreen:
@Composable
fun SelectUserScreen(
    viewmodel : SelectUserScreenViewModel = koinViewModel()
) {

    val uiState by viewmodel.uiState.collectAsStateWithLifecycle()
    val context = LocalContext.current
    val appNavigator = LocalAppNavigator.current

    LaunchedEffect(Unit) {
        viewmodel.uiActionState.collect {
            when(it) {
                is SelectUserScreenEffect.ReturnSelectUser -> {
                    val navController = appNavigator.provideNavController()
                    val previousEntry = navController.previousBackStackEntry
                    previousEntry?.savedStateHandle?.set("selected_user", it.userInfo)
                    appNavigator.navigateBack()
                }
                is SelectUserScreenEffect.ShowToast -> {
                    Toast.makeText(context, it.message, Toast.LENGTH_SHORT).show()
                }
            }
        }
    }

    SelectUserContent(
        uiState = uiState,
        onEvent = viewmodel::onEvent
    )
}
  1. SendGiftScreenViewModel: can't get the value in ViewModel
class SendGiftScreenViewModel(
    private val savedStateHandle: SavedStateHandle
) : BaseActionStateViewModel<SendGiftScreenUIState, SendGiftScreenUIEvent, SendGiftScreenEffect>(),
    KoinComponent {

    private val wishRepository: XXXRepository by inject()

    init {
        // This can't get the value from SavedStateHandle when SelectUserScreen back
        viewModelScope.launch {
            savedStateHandle.getStateFlow<SimpleUser?>("selected_user", null)
                .filterNotNull()
                .collect { user ->
                    setState { copy(targetUser = user) }
                    
                    savedStateHandle.remove<SimpleUser?>("selected_user")
                }
        }
    }
}
  1. SendGiftScreen : this can get this value
@Composable
fun SendGiftScreen(
    viewmodel: SendGiftScreenViewModel = koinViewModel()
) {

    val uiState by viewmodel.uiState.collectAsStateWithLifecycle()
    val appNavigator = LocalAppNavigator.current

    LaunchedEffect(Unit) {
        val navController = appNavigator.provideNavController()
        val savedStateHandle = navController.currentBackStackEntry?.savedStateHandle

        // this can get  the value
        savedStateHandle?.getStateFlow<SimpleUser?>("selected_user", null)
            ?.collect { user ->

                if (user != null) {
                    // Update ViewModel state
                    viewmodel.onEvent(SendGiftScreenUIEvent.OnUserSelected(user))
                    // Clear the result
                    savedStateHandle.remove<SimpleUser?>("selected_user")
                }
            }
    }
  1. AppNavigator:
class AppNavigatorImpl(
    private val navController: NavController
) : AppNavigator {

    override fun navigateTo(
        screen: Screen,
        popUpTo: Screen?,
        inclusive: Boolean,
        singleTop: Boolean,
        restoreState: Boolean,
        saveState: Boolean
    ) {
        navController.navigate(screen) {
            launchSingleTop = singleTop
            if (restoreState) this.restoreState = true
            popUpTo?.let {
                popUpTo(it) {
                    this.inclusive = inclusive
                    if (saveState) this.saveState = true
                }
            }
        }
    }

    override fun navigateBack() : Boolean{
        return navController.popBackStack()
    }

    override fun navigateToWithPopAll(screen: Screen) {
        navController.navigate(screen) {
            popUpTo(navController.graph.findStartDestination().id) {
                inclusive = true
            }
            launchSingleTop = true
        }
    }

    override fun provideNavController(): NavController {
        return navController
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions