Leave a rating/review
Notes: 09. Apply Error & Data Handling to the UI
The student materials have been reviewed and are updated as of September 2022.
In ui/bookReviewDetails/BookReviewDetailsActivity file, within BookRevBookReviewDetailsInformationiewItem() composable function, replace CoilImage() composable by AsyncImage() composable with updated parameters. CoilImage composable is old and not compatible with current Jetpack Compose version.
Demo
To make sure your forms don’t let the user store empty data, you need a way to disable actions or show errors, when the data is invalid. To do that, you need to apply error and data handling!
Open the AddBookActivity, and add the following changes, to apply error handling to your screen:
InputField(
value = bookNameState.value,
...
isInputValid = bookNameState.value.isNotEmpty()
)
InputField(
value = bookDescriptionState.value,
...
isInputValid = bookDescriptionState.value.isNotEmpty()
)
ActionButton(
...
isEnabled = bookNameState.value.isNotEmpty()
&& bookDescriptionState.value.isNotEmpty()
&& _addBookState.value.genreId.isNotEmpty()
)
Now open the AddBookReviewActivity, and do the same:
isInputValid = bookUrl.value.isNotEmpty()
...
isInputValid = bookNotes.value.isNotEmpty()
...
val pickedBook = _bookReviewState.value?.bookAndGenre
isEnabled = bookNotes.value.isNotEmpty() && bookUrl.value.isNotEmpty()
&& pickedBook != null && pickedBook != EMPTY_BOOK_AND_GENRE
By adding these data handling operations, to update the validity of components such as input fields and action buttons, you can turn error colors on or off, or enable and disable the use of buttons.
[Open ActionButton & InputField]
These options are available within the custom components you’ve built, but they are native to both the OutlinedInputField and all the Button composables.
Now build & run the app, and check out your input fields and buttons in these activities.
[Build & Run, check error handling]
The UI looks almost same, as the buttons are now disabled by default, and greyed out! Also, once you start typing or focusing the input fields, they are marked with the red error color, until you write something, and meet the requirements.
This is a good way to tell the user their input is not valid. You could also add a special error message underneath each input field, if you have specific requirements, like having the password be at least six characters, to make it easier for the user to understand.
With this input, it’s easy to see what’s going on, as the text input is red only if the input is empty! Pretty cool! :]
Now that you have the error handling implemented in your screens where you add data, you can move on to practice some more Compose, and build another screen, to show the details of each BookReview!
Open the BookReviewDetailsActivity, and start off by changing the following code:
private val _bookReviewDetailsState = mutableStateOf(EMPTY_BOOK_REVIEW)
private val _genreState = mutableStateOf(EMPTY_GENRE)
You’ve switched from the LiveData construct to a state instead, to be able to update the UI, just like you did before.
Now let’s build the BookReviewDetailsInformation composable. Start off by adding the required data:
val bookReview = _bookReviewDetailsState.value
val genre = _genreState.value
These two pieces of information will be important, as you need to show the data within the Details screen. Next, add the root component:
Column(
modifier = Modifier
.fillMaxSize()
.scrollable(rememberScrollState(), orientation = Orientation.Vertical),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Spacer(modifier = Modifier.height(16.dp))
By using a Column with the scrollable() modifier, you can build vertical items, and have the component scrollable in case they take up more space then the screen has. This is similar to a ScrollView in the standard toolkit. You also added a spacer to add some margins to the first element.
Now add the following code:
Notes
Card(
modifier = Modifier
.size(width = 200.dp, height = 300.dp),
shape = RoundedCornerShape(16.dp),
elevation = 16.dp
) {
CoilImage(
data = bookReview.review.imageUrl,
contentScale = ContentScale.FillWidth,
contentDescription = null
)
}
Spacer(modifier = Modifier.height(16.dp))
Text(
text = bookReview.book.name,
fontWeight = FontWeight.Bold,
fontSize = 18.sp
)
Spacer(modifier = Modifier.height(6.dp))
Text(
text = genre.name,
fontSize = 12.sp
)
The first component is another image, that loads the data from the network, using the imageUrl you provided within the review. You style the card to have all four corners rounded, and a large elevation to provide that 3D look and feel.
Other than that, you added more spacers between each element, and added a text to describe the name of the book, and its genre.
Now add the next piece of code, to fill in the data:
Spacer(modifier = Modifier.height(6.dp))
RatingBar(
modifier = Modifier.align(CenterHorizontally),
range = 1..5,
isSelectable = false,
isLargeRating = false,
currentRating = bookReview.review.rating
)
Spacer(modifier = Modifier.height(6.dp))
Text(
text = stringResource(
id = R.string.last_updated_date,
formatDateToText(bookReview.review.lastUpdatedDate)
),
fontSize = 12.sp
)
Spacer(modifier = Modifier.height(8.dp))
Finally, build the following spacers to add separators from the main body of data and the user’s review notes.
Spacer(
modifier = Modifier
.fillMaxWidth(0.9f)
.height(1.dp)
.background(
brush = SolidColor(value = Color.LightGray),
shape = RectangleShape
)
)
Text(
modifier = Modifier.padding(start = 20.dp, end = 20.dp, top = 8.dp, bottom = 8.dp),
text = bookReview.review.notes,
fontSize = 12.sp,
fontStyle = FontStyle.Italic
)
Spacer(
modifier = Modifier
.fillMaxWidth(0.9f)
.height(1.dp)
.background(
brush = SolidColor(value = Color.LightGray),
shape = RectangleShape
)
)
Lots of code here, but it’s mostly just spacing! You added the rating bar to show how good you thought the book was, the last updated date, to show when you last updated the review, which will be important when you build the ReadingEntriesList part of the UI, later in the course. You also added the notes, surrounded by special Spacers.
These Spacers have a height, they almost take up the entire width, by using the fraction parameter to the fillMaxWidth() modifier, and they have a special background modifier. This BG modifier paints the spacer with a LightGray color, adding a line separator around the review’s notes text.
Now build and run your app, to see the results!
[Build & Run the app]
The details screen looks awesome! :] You’ve really built some cool components and UI using Compose, without actually having to write a lot of complex logic or code! You just used the basic components, and some modifiers. This is the real power of Jetpack Compose - you get to build awesome UI with minimal effort! :]