You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose导航异常:启动自动跳转失效及页面空白

问题修复:Jetpack Compose 导航异常与自动跳转失效

我希望应用启动时自动导航到SearchResults页面,于是在ViewModel的init块里写了修改uiState的逻辑,还在NavHost里加了条件判断触发导航,但现在遇到两个问题:

  • 应用启动后无法自动跳转到结果页,必须手动点击搜索按钮
  • 点击搜索按钮后页面空白,只显示Search组件和顶部应用栏,点击返回按钮后才恢复正常

相关代码

FlightApp:

@Composable
fun FlightApp() {
    val viewModel: FlightsViewModel = viewModel(factory = FlightsViewModel.Factory)
    val uiState = viewModel.uiState.collectAsState()
    val navController: NavHostController = rememberNavController()
    val backStackEntry by navController.currentBackStackEntryAsState()
    val currentScreen = FlightScreen.valueOf(
        backStackEntry?.destination?.route ?: FlightScreen.Home.name
    )
    val coroutineScope = rememberCoroutineScope()
    Scaffold(
        topBar = {
            AppBar(
                onBackButtonClick = {
                    navController.navigateUp()
                    //viewModel.resetSearchState()
                },
                currentScreen = currentScreen,
                canNavigateBack = navController.previousBackStackEntry != null
            )
        }
    ) {innerPadding ->
        Column(modifier = Modifier.padding(innerPadding)) {
            Search(
                query = uiState.value.initialQuery,
                onQueryChange = { viewModel.onQueryChange(it) },
                onSearch = {
                    coroutineScope.launch {
                        viewModel.onSearch()
                    }
                    navController.navigate(FlightScreen.SearchResults.name)
                },
                onSuggestionClick = {
                    coroutineScope.launch {
                        viewModel.getSearchResults(it)
                    }
                    navController.navigate(FlightScreen.SearchResults.name)
                },
                isActive = uiState.value.searchState == SearchState.Searching,
                airportSuggestions = uiState.value.airportSuggestions
            )
            NavHost(
                navController = navController,
                startDestination = FlightScreen.Home.name,
                modifier = Modifier
                    .fillMaxSize()
                    .padding(8.dp)
            ) {
                composable(route = FlightScreen.Home.name) {
                    HomeScreen(
                        favouriteRoutes = uiState.value.favourites,
                        onFavouriteChange = { viewModel.onFavouriteChange(it) }
                    )
                }
                composable(route = FlightScreen.SearchResults.name) {
                    SearchResults(
                        routes = uiState.value.routes,
                        onFavouriteChange = { viewModel.onFavouriteChange(it) }
                    )
                }
                // 问题来源:此处的条件导航写法错误
                if (uiState.value.searchState == SearchState.Searched && uiState.value.routes.isNotEmpty()) {
                    navController.navigate(FlightScreen.SearchResults.name)
                    viewModel.onSearch()
                }
            }
        }
    }
}

ViewModel:

class FlightsViewModel(
    val flightRepository: FlightRepository
): ViewModel() {
    private val _uiState = MutableStateFlow(FlightsUiState(SearchState.NoSearch, listOf(), "", listOf(), listOf()))
    val uiState = _uiState.asStateFlow()

    init {
        viewModelScope.launch {
            updateFavourites()
        }
        viewModelScope.launch {
            initialSearch()
        }
    }

    private suspend fun initialSearch() {
        flightRepository.lastQuery.collect {lastQuery ->
            _uiState.update {flightsUiState ->
                flightsUiState.copy(
                    initialQuery = lastQuery
                )
            }
            if (_uiState.value.initialQuery != "") {
                onQueryChange(_uiState.value.initialQuery)
                onSearch()
            }
        }
    }

    fun onQueryChange(query: String) {
        viewModelScope.launch {
            flightRepository.getSuggestions(query).collect { airportSuggestions ->
                _uiState.update { flightsUiState ->
                    flightsUiState.copy(
                        initialQuery = query,
                        searchState = SearchState.Searching,
                        airportSuggestions = airportSuggestions
                    )
                }
            }
        }
        viewModelScope.launch {
            flightRepository.saveLastQuery(query)
        }
    }

    fun onSearch() {
        _uiState.update {
            it.copy(
                searchState = SearchState.Searched
            )
        }
        if (_uiState.value.airportSuggestions.size == 1) {
            viewModelScope.launch {
                getSearchResults(_uiState.value.airportSuggestions[0])
            }
        }
    }

    private suspend fun updateFavourites() {
        flightRepository.getFavourites().collect {favourites ->
            _uiState.update { flightsUiState ->
                flightsUiState.copy(
                    favourites = favourites
                )
            }
        }
    }

    fun onFavouriteChange(route: Route) {
        viewModelScope.launch {
            if (route.isFavourite == 1) {
                flightRepository.onFavouriteChange(route.copy(isFavourite = 0))
            } else {
                flightRepository.onFavouriteChange(route.copy(isFavourite = 1))
            }
            updateFavourites()
        }
    }

    suspend fun getSearchResults(departureAirport: Airport) {
        flightRepository.getSearchResults(departureAirport).collect {routes ->
            _uiState.update {flightsUiState ->
                flightsUiState.copy(
                    searchState = SearchState.Searched,
                    routes = routes
                )
            }
        }
    }

    companion object {
        val Factory = viewModelFactory {
            initializer {
                val application = (this[APPLICATION_KEY] as FlightApplication)
                FlightsViewModel(application.container.flightRepository)
            }
        }
    }
}

data class FlightsUiState(
    val searchState: SearchState = SearchState.NoSearch,
    val favourites: List<Route>,
    val initialQuery: String,
    val airportSuggestions: List<Airport>,
    val routes: List<Route>
)

enum class SearchState {
    NoSearch,
    Searching,
    Searched
}

SearchResults:

@Composable
fun SearchResults(
    routes: List<Route>,
    onFavouriteChange: (Route) -> Unit
) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        if (routes.isNotEmpty()) {
            val departureCode = routes[0].departureCode
            Text(
                text = stringResource(R.string.flights_from, departureCode),
                fontWeight = FontWeight.Bold,
                modifier = Modifier.padding(vertical = 16.dp)
            )
            FlightsColumn(routes = routes) { onFavouriteChange(it) }
        } else {
            Text(
                text = stringResource(R.string.no_results),
                textAlign = TextAlign.Center
            )
        }
    }
}

问题原因分析

  1. 自动导航失效:NavHost内部的条件导航代码是错误用法。NavHost的代码块仅用于定义路由页面,在此处写业务逻辑会导致重组时反复执行,且可能因NavHost未完全初始化而导致导航不生效。
  2. 点击搜索后页面空白:
    • 点击搜索时先执行navigate跳转,再异步调用viewModel.onSearch(),此时SearchResults页面拿到的routes尚未加载完成,因此显示空白
    • NavHost内的错误导航逻辑会造成导航栈混乱,进一步加剧页面显示异常

修复方案

1. 删除NavHost内的错误导航代码

直接移除NavHost中的这段代码:

if (uiState.value.searchState == SearchState.Searched && uiState.value.routes.isNotEmpty()) {
    navController.navigate(FlightScreen.SearchResults.name)
    viewModel.onSearch()
}

2. 使用LaunchedEffect实现安全自动导航

在FlightApp中添加LaunchedEffect,监听uiState变化,满足条件时触发导航(避免重复跳转):

// 放在Scaffold上方或NavHost外部
LaunchedEffect(uiState.value, currentScreen) {
    val shouldNavigate = uiState.value.searchState == SearchState.Searched 
            && uiState.value.routes.isNotEmpty()
            && currentScreen != FlightScreen.SearchResults
    if (shouldNavigate) {
        navController.navigate(FlightScreen.SearchResults.name)
    }
}

3. 修正搜索按钮的导航逻辑

移除搜索回调中的直接导航,交给LaunchedEffect统一处理,避免时序问题:

onSearch = {
    coroutineScope.launch {
        viewModel.onSearch()
    }
    // 删除此处的navigate代码
}

onSuggestionClick = {
    coroutineScope.launch {
        viewModel.getSearchResults(it)
    }
    // 删除此处的navigate代码
}

4. 优化ViewModel初始搜索逻辑

确保初始搜索时,建议列表加载完成后再执行搜索,保证状态更新的正确性:

private suspend fun initialSearch() {
    flightRepository.lastQuery.collect { lastQuery ->
        if (lastQuery.isNotEmpty()) {
            _uiState.update { it.copy(initialQuery = lastQuery) }
            // 等待建议列表加载完成
            val suggestionsJob = viewModelScope.launch {
                flightRepository.getSuggestions(lastQuery).collect { suggestions ->
                    _uiState.update {
                        it.copy(
                            airportSuggestions = suggestions,
                            searchState = SearchState.Searching
                        )
                    }
                }
            }
            suggestionsJob.join()
            onSearch()
            flightRepository.saveLastQuery(lastQuery)
        }
    }
}

5. 可选:调整页面布局

如果Search组件不需要在所有页面显示,可将其移入HomeScreen,避免导航到SearchResults时仍显示Search组件。


修改后的完整FlightApp代码

@Composable
fun FlightApp() {
    val viewModel: FlightsViewModel = viewModel(factory = FlightsViewModel.Factory)
    val uiState = viewModel.uiState.collectAsState()
    val navController: NavHostController = rememberNavController()
    val backStackEntry by navController.currentBackStackEntryAsState()
    val currentScreen = FlightScreen.valueOf(
        backStackEntry?.destination?.route ?: FlightScreen.Home.name
    )
    val coroutineScope = rememberCoroutineScope()

    // 自动导航逻辑
    LaunchedEffect(uiState.value, currentScreen) {
        val shouldNavigate = uiState.value.searchState == SearchState.Searched 
                && uiState.value.routes.isNotEmpty()
                && currentScreen != FlightScreen.SearchResults
        if (shouldNavigate) {
            navController.navigate(FlightScreen.SearchResults.name)
        }
    }

    Scaffold(
        topBar = {
            AppBar(
                onBackButtonClick = {
                    navController.navigateUp()
                    //viewModel.resetSearchState()
                },
                currentScreen = currentScreen,
                canNavigateBack = navController.previousBackStackEntry != null
            )
        }
    ) { innerPadding ->
        Column(modifier = Modifier.padding(innerPadding)) {
            Search(
                query = uiState.value.initialQuery,
                onQueryChange = { viewModel.onQueryChange(it) },
                onSearch = {
                    coroutineScope.launch {
                        viewModel.onSearch()
                    }
                },
                onSuggestionClick = {
                    coroutineScope.launch {
                        viewModel.getSearchResults(it)
                    }
                },
                isActive = uiState.value.searchState == SearchState.Searching,
                airportSuggestions = uiState.value.airportSuggestions
            )
            NavHost(
                navController = navController,
                startDestination = FlightScreen.Home.name,
                modifier = Modifier
                    .fillMaxSize()
                    .padding(8.dp)
            ) {
                composable(route = FlightScreen.Home.name) {
                    HomeScreen(
                        favouriteRoutes = uiState.value.favourites,
                        onFavouriteChange = { viewModel.onFavouriteChange(it) }
                    )
                }
                composable(route = FlightScreen.SearchResults.name) {
                    SearchResults(
                        routes = uiState.value.routes,
                        onFavouriteChange = { viewModel.onFavouriteChange(it) }
                    )
                }
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者Anas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 07:55:55