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 = ""
)

Большое спасибо тому, кто это читает!

0 ответов

Другие вопросы по тегам