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