NavHost, предотвращающий обновления пользовательского интерфейса
Я использую Retrofit в своем проекте для выполнения запросов к API Google Книг, и если я держу его вне NavHost, он работает нормально; поиск книги возвращает список книг, который отображается в пользовательском интерфейсе. Однако, когда я помещаю тот же компонуемый в NavHost, он показывает только начальный экран для компонуемого (предварительный поиск), а поиск книги приводит к тому, что пользовательский интерфейс остается прежним.
Я много раз использовал NavHosts в своих проектах и еще не сталкивался с подобной проблемой. Простое хранение компонуемого вне NavHost позволяет ему работать нормально, но его установка полностью останавливает обновления пользовательского интерфейса.
Сначала я подумал, что запрос просто не поступает к API, но использование инструмента отладки показало, что нажатие кнопки на самом деле приводит к успешному ответу API. Ниже приведен код компонуемого объекта, содержащего NavHost, компонуемого объекта с пользовательским интерфейсом, который не обновляется, а также соответствующей модели представления. Обратите внимание, что перемещение «HomeScreen» из NavHost сразу после innerPadding заставляет его работать нормально, но, конечно, мешает мне использовать NavHost для навигации.
Компонуемый, содержащий NavHost
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BookSearchApp(viewModel: BookSearchViewModel = hiltViewModel()) {
val navController = rememberNavController()
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = stringResource(R.string.book_search)) },
navigationIcon = {
if (navController.previousBackStackEntry != null)
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(R.string.back_arrow)
)
}
)
},
bottomBar = {
BottomAppBar() {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
val focusManger = LocalFocusManager.current
OutlinedTextField(
value = viewModel.searchTextUiState.searchText,
onValueChange = { viewModel.updateSearchTextUiState(it) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManger.clearFocus() }),
singleLine = true,
modifier = Modifier.width(200.dp)
)
Spacer(modifier = Modifier.width(10.dp))
Button(onClick = { viewModel.getBooks(); focusManger.clearFocus() }) {
Text(text = stringResource(R.string.search))
}
}
}
}) { innerPadding ->
NavHost(
navController = navController,
startDestination = "HomeScreen",
modifier = Modifier.padding(innerPadding)
) {
composable(route = "HomeScreen") {
HomeScreen(onBookClick = { navController.navigate("BookDataScreen") })
}
composable(route = "BookDataScreen") {
BookDataScreen()
}
}
}
}
Компонуется с пользовательским интерфейсом, который обновляется, если он находится за пределами NavHost, и не обновляется, если внутри него.
/**
* Home screen that allows users to query books using the Google Books API.
* [HomeScreenUiState] determines which screen to display, based on the status of the query.
*/
@Composable
fun HomeScreen(
viewModel: BookSearchViewModel = hiltViewModel(),
onBookClick: () -> Unit,
modifier: Modifier = Modifier
) {
when (viewModel.homeScreenUiState) {
is HomeScreenUiState.Loading -> LoadingScreen()
is HomeScreenUiState.Success -> HomeBody(
bookData = (viewModel.homeScreenUiState as HomeScreenUiState.Success).bookData,
onBookClick = onBookClick,
modifier = modifier
)
is HomeScreenUiState.Start -> StartScreen()
else -> ErrorScreen(retryAction = { viewModel.getBooks() })
}
}
/**
* Body for [HomeScreen].
*/
@Composable
private fun HomeBody(bookData: GetBooks, onBookClick: () -> Unit, modifier: Modifier = Modifier) {
Column(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier.padding(6.dp)
) {
BookList(bookData = bookData, onBookClick = onBookClick)
}
}
/**
* Displays a lazy vertical grid of queried book thumbnails if [bookData] is not null, or [InvalidSearch] if null.
*/
@Composable
private fun BookList(bookData: GetBooks, onBookClick: () -> Unit) {
if (bookData.bookItems != null) {
LazyVerticalGrid(
horizontalArrangement = Arrangement.Center,
columns = GridCells.Adaptive(minSize = 150.dp),
) {
items(items = bookData.bookItems) { bookItem ->
BookListEntry(volumeInfo = bookItem.volumeInfo, onBookClick = onBookClick)
}
}
} else {
InvalidSearch()
}
}
/**
* Composable that displays in [BookList] if a search returns books.
*/
@Composable
private fun BookListEntry(volumeInfo: VolumeInfo, onBookClick: () -> Unit, modifier: Modifier = Modifier) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Column(
modifier = modifier
.padding(6.dp)
) {
AsyncImage(
model = ImageRequest.Builder(context = LocalContext.current)
.data(volumeInfo.imageLinks?.thumbnail) //Must use a safe call in case the thumbnail is null
.crossfade(true)
.build(),
contentDescription = null,
contentScale = ContentScale.FillBounds,
error = painterResource(id = R.drawable.ic_broken_image),
placeholder = painterResource(id = R.drawable.loading_img),
fallback = painterResource(id = R.drawable.ic_broken_image),
modifier = modifier
.height(200.dp)
.width(150.dp)
.clickable { }
)
}
}
}
Просмотр модели для предыдущих блоков кода
/**
* UI state for [HomeScreen].
*/
sealed interface HomeScreenUiState {
data class Success(val bookData: GetBooks) : HomeScreenUiState
object Error : HomeScreenUiState
object Loading : HomeScreenUiState
object Start : HomeScreenUiState
}
**View Model for the previous composables**
/**
* View Model for [HomeScreen].
*/
@HiltViewModel
class BookSearchViewModel @Inject constructor(private val bookRepository: BookRepository) :
ViewModel() {
/**
* Holds [HomeScreenUiState].
*/
var homeScreenUiState: HomeScreenUiState by mutableStateOf(HomeScreenUiState.Start)
private set
/**
* Uses [bookRepository] to query the Google Books API with [searchTextUiState].
*/
fun getBooks() {
viewModelScope.launch {
homeScreenUiState = HomeScreenUiState.Loading
homeScreenUiState = try {
HomeScreenUiState.Success(bookRepository.getBooks(searchTextUiState.searchText))
} catch (e: IOException) {
HomeScreenUiState.Error
} catch (e: HttpException) {
HomeScreenUiState.Error
}
}
}
/**
* Holds [searchTextUiState] for the text field from [HomeScreen].
*/
var searchTextUiState by mutableStateOf(SearchTextUiState())
private set
/**
* Updates [searchTextUiState] with values provided by the text field from [HomeScreen].
*/
fun updateSearchTextUiState(searchText: String) {
searchTextUiState = SearchTextUiState(searchText = searchText)
}
}
/**
* Ui state for the text field from [HomeScreen].
*/
data class SearchTextUiState(
val searchText: String = ""
)
Большое спасибо тому, кто это читает!