Search code examples
androidkotlinandroid-jetpack-compose

Navigation takes me to an empty page


I want my app to navigate to SearchResults when I first open it so I put the logic to change the uiState in the ViewModel init block. Then I used an if statement to check the ViewModel and navigate to SearchResults if certain conditions are met. Then when I run the code it doesn't automatically take me to the results page I have to manually click the search button.

In addition to that when I do click the search button the page is blank and only has the search composable and the top app bar (left), when I click the back button the page becomes normal (right):

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 statement checks for conditions then navigates
                //edit: this was the cause of the problem and it's fixed in the answer. 

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

Above is the if statement I am talking about

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
            )
        }
    }
}

Solution

  • I found out that the problem was with an if statement so I fixed it and editied my viewmodel a bit.

    class FlightsViewModel(
        private val flightRepository: FlightRepository
    ): ViewModel() {
    
    //some code
    
        private suspend fun initialSearch() {
            flightRepository.lastQuery.collect {lastQuery ->
                _uiState.update {flightsUiState ->
                    flightsUiState.copy(
                        initialQuery = lastQuery,
    
                        //added this
                        doInitialSearch = true  
                    )
                }
                if (_uiState.value.initialQuery != "") {
                    onQueryChange(_uiState.value.initialQuery)
                    onSearch()
                }
            }
        }
        //new function
        fun initialSearchDone() {              
            _uiState.update {
                it.copy(doInitialSearch = false)
            }
        }
    //some code
    
    }
    data class FlightsUiState(
        val searchState: SearchState = SearchState.NoSearch,
        val favourites: List<Route>,
        val initialQuery: String,
        val airportSuggestions: List<Airport>,
        val routes: List<Route>,
    
        //added this variable
        var doInitialSearch: Boolean   
    )
    

    The if statement now looks like this:

    if (uiState.value.doInitialSearch) {
        navController.navigate(FlightScreen.SearchResults.name)
        viewModel.initialSearchDone()
    }