Good morning,
I’m currently trying to solve the Bookshelf app project proposed by Google.
I think I didn’t really understand some concepts and that’s the reason why I encountered the following issue.
The idea of the app is to download a list of books with an API, to display the images of the books on the screen and beeing able to click on one of them to display the book information.
I’m able to download the books and display them on the screen. However, when I click on one of them, It seems that an infinite loop starts and “getBook” is always called. It seems that the bookUiState is always “Loading” but I suppose it’s because of the infinite loop.
Could you help me ?
Do you have some advice of “good practices” ?
Here is my code, If you think that the issue is coming from another place I’ll post my entire code, but I think it’ll be in this part.
BookshelfNavHost
@Composable
fun BookshelfNavHost(viewModel : BookshelfViewModel,
navController: NavHostController,
modifier: Modifier = Modifier) {
NavHost(
navController = navController,
startDestination = AppScreen.Home.name,
modifier = modifier
) {
//val viewModel : BookshelfViewModel = viewModel(factory = BookshelfViewModel.Factory)
composable(route = AppScreen.Home.name) {
HomeScreen(
bookshelfUiState = viewModel.bookshelfUiState,
onClick = {bookId ->
Log.d("NavHost", viewModel.getBook().toString())
viewModel.updateCurrentSelectedBookId(bookId)
viewModel.getBook()
Log.d("NavHost", viewModel.getBook().toString())
navController.navigate(AppScreen.Details.name)}
)
}
composable(route = AppScreen.Details.name) {
Log.d("NavHost", viewModel.getBook().toString())
DetailsScreen(viewModel.selectedBookId,
bookUiState = viewModel.bookUiState,
)
}
}
}
BookshelfViewModel.kt
sealed interface BookshelfUiState {
data class Success(val bookshelf: Bookshelf?) : BookshelfUiState
object Error : BookshelfUiState
object Loading : BookshelfUiState
}
sealed interface BookUiState {
data class Success(val book: Book?) : BookUiState
object Error : BookUiState
object Loading : BookUiState
}
class BookshelfViewModel(private val bookshelfRepository: BookshelfRepository
) : ViewModel() {
/** The mutable State that stores the status of the most recent request */
var bookshelfUiState: BookshelfUiState by mutableStateOf(BookshelfUiState.Loading)
private set
var bookUiState: BookUiState by mutableStateOf(BookUiState.Loading)
private set
var selectedBookId : String by mutableStateOf("")
init {
getBookshelf()
}
fun getBookshelf() {
viewModelScope.launch {
bookshelfUiState = BookshelfUiState.Loading
bookshelfUiState = try {
BookshelfUiState.Success(bookshelfRepository.getBookshelf())
} catch (e: IOException) {
BookshelfUiState.Error
} catch (e: HttpException) {
BookshelfUiState.Error
}
}
}
fun getBook() {
Log.d("getBook", selectedBookId)
viewModelScope.launch {
bookUiState = BookUiState.Loading
bookUiState = try {
BookUiState.Success(bookshelfRepository.getBook(selectedBookId))
} catch (e: IOException) {
BookUiState.Error
} catch (e: HttpException) {
BookUiState.Error
}
}
}
fun updateCurrentSelectedBookId(newSelectedBookId: String) {
selectedBookId = newSelectedBookId
}
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application = (this[APPLICATION_KEY] as BookshelfApplication)
val bookshelfRepository = application.container.bookshelfRepository
BookshelfViewModel(bookshelfRepository = bookshelfRepository)
}
}
}
}
DetailsScreen.kt
@Composable
fun DetailsScreen(bookId : String,
bookUiState: BookUiState,
modifier : Modifier = Modifier){
when (bookUiState) {
is BookUiState.Loading -> {
Log.d("DetailsScreen", "Loading Screen")
LoadingScreen(modifier = modifier.fillMaxSize())
}
is BookUiState.Success ->
if (bookUiState.book != null) {
Log.d("DetailsScreen", bookUiState.book.toString())
DetailsScreenContent(bookUiState.book?.volumeInfo!!,
modifier = modifier.fillMaxWidth())
} else {
ErrorScreen( modifier = modifier.fillMaxSize())
}
is BookUiState.Error -> ErrorScreen( modifier = modifier.fillMaxSize())
}
}