1. Trước khi bắt đầu
Giới thiệu
Trong các lớp học lập trình trước, bạn đã tìm hiểu cách lấy dữ liệu qua một dịch vụ web bằng cách sử dụng mẫu kho lưu trữ và phân tích cú pháp phản hồi thành một đối tượng Kotlin. Trong lớp học lập trình này, bạn sẽ phát huy kiến thức đó để tải và hiển thị ảnh lấy từ một URL. Bạn cũng sẽ ôn lại cách tạo và sử dụng LazyVerticalGrid
để trình bày hình ảnh theo bố cục lưới trên trang tổng quan.
Điều kiện tiên quyết
- Có kiến thức về cách truy xuất JSON qua dịch vụ web REST và việc phân tích cú pháp dữ liệu đó thành đối tượng trong Kotlin bằng cách dùng thư viện Retrofit và Gson
- Có kiến thức về dịch vụ web REST
- Quen thuộc với bộ thành phần cấu trúc Android, chẳng hạn như lớp dữ liệu và kho lưu trữ
- Có kiến thức về chèn phần phụ thuộc
- Có kiến thức về
ViewModel
vàViewModelProvider.Factory
- Có kiến thức về cách triển khai coroutine cho ứng dụng
- Có kiến thức về mẫu kho lưu trữ
Kiến thức bạn sẽ học được
- Cách sử dụng thư viện Coil để tải và hiển thị hình ảnh lấy từ một URL.
- Cách sử dụng
LazyVerticalGrid
để hiển thị hình ảnh theo bố cục lưới. - Cách xử lý các lỗi có thể xảy ra khi tải và hiển thị hình ảnh.
Sản phẩm bạn sẽ tạo ra
- Sửa đổi ứng dụng Mars Photos (Ảnh sao Hoả) để lấy URL hình ảnh từ dữ liệu về sao Hoả, sau đó sử dụng thư viện Coil để tải và hiển thị hình ảnh đó.
- Thêm ảnh động thể hiện trạng thái đang tải và biểu tượng lỗi vào ứng dụng.
- Thêm trạng thái và mã xử lý lỗi vào ứng dụng.
Bạn cần có
- Một máy tính sử dụng trình duyệt web hiện đại (chẳng hạn như trình duyệt Chrome phiên bản mới nhất)
- Mã khởi đầu cho ứng dụng Mars Photos cùng với dịch vụ web REST
2. Tổng quan về ứng dụng
Trong lớp học lập trình này, bạn sẽ tiếp tục làm việc với ứng dụng Mars Photos từ lớp học lập trình trước. Ứng dụng Mars Photos kết nối với một dịch vụ web để truy xuất và hiển thị số lượng đối tượng Kotlin truy xuất được bằng Gson. Các đối tượng Kotlin này chứa URL của ảnh thật về bề mặt sao Hoả, được chụp từ Thiết bị thám hiểm sao Hoả của NASA.
Phiên bản ứng dụng mà bạn tạo trong lớp học lập trình này sẽ hiển thị ảnh chụp sao Hoả theo bố cục lưới. Những hình ảnh này nằm trong dữ liệu mà ứng dụng của bạn truy xuất từ dịch vụ web. Ứng dụng của bạn sẽ sử dụng thư viện Coil để tải và hiển thị hình ảnh, đồng thời sử dụng LazyVerticalGrid
để tạo bố cục lưới cho ảnh. Ứng dụng của bạn cũng sẽ xử lý lỗi mạng một cách linh hoạt bằng cách hiển thị thông báo lỗi.
Lấy mã khởi đầu
Để bắt đầu, hãy tải mã khởi đầu xuống:
Ngoài ra, bạn có thể sao chép kho lưu trữ GitHub cho mã:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos.git $ cd basic-android-kotlin-compose-training-mars-photos $ git checkout coil-starter
Bạn có thể xem mã này trong kho lưu trữ GitHub Mars Photos
.
3. Hiển thị hình ảnh đã tải xuống
Việc hiển thị hình ảnh lấy từ URL nghe có vẻ đơn giản, nhưng có khá nhiều kỹ thuật cần áp dụng để hình ảnh hoạt động tốt. Hình ảnh phải được tải xuống, lưu trữ nội bộ (vào bộ nhớ đệm) và giải mã từ định dạng nén thành hình ảnh mà Android có thể sử dụng. Bạn có thể lưu hình ảnh vào bộ nhớ đệm của bộ nhớ trong, bộ nhớ đệm trên thành phần lưu trữ hoặc cả hai. Toàn bộ quá trình này phải diễn ra trong luồng có mức độ ưu tiên thấp ở chế độ nền, để giao diện người dùng vẫn có thể phản hồi. Ngoài ra, để có chất lượng kết nối mạng và hiệu suất CPU tốt nhất, bạn nên tìm nạp và giải mã nhiều hình ảnh cùng một lúc.
May mắn là bạn có thể sử dụng một thư viện do cộng đồng phát triển có tên là Coil để tải xuống, lưu vào bộ đệm, giải mã và lưu hình ảnh vào bộ nhớ đệm. Nếu không dùng Coil, bạn sẽ phải làm nhiều việc hơn.
Về cơ bản, Coil cần hai thứ:
- URL của hình ảnh bạn muốn tải và hiển thị.
- Một thành phần kết hợp
AsyncImage
để hiển thị hình ảnh đó.
Trong nhiệm vụ này, bạn sẽ tìm hiểu cách sử dụng Coil để hiển thị một hình ảnh từ dịch vụ web về sao Hoả. Bạn cho hiện ảnh chụp sao Hoả nằm đầu tiên trong danh sách mà dịch vụ web trả về. Các hình ảnh sau hiển thị ảnh chụp màn hình trước và sau:
Thêm phần phụ thuộc từ Coil
- Mở ứng dụng giải pháp Mars Photos từ lớp học lập trình Thêm kho lưu trữ và DI thủ công.
- Chạy ứng dụng này để xác nhận rằng ứng dụng hiển thị số lượng ảnh chụp sao Hoả đã truy xuất.
- Mở build.gradle.kts (Module :app).
- Trong phần
dependencies
, hãy thêm dòng này để gọi thư viện Coil:
// Coil
implementation("io.coil-kt:coil-compose:2.4.0")
Kiểm tra và cập nhật lên phiên bản mới nhất của thư viện này trên trang tài liệu về Coil.
- Nhấp vào Sync Now (Đồng bộ hoá ngay) để tạo lại dự án với phần phụ thuộc mới.
Hiển thị URL hình ảnh
Ở bước này, bạn truy xuất và hiển thị URL của ảnh chụp sao Hoả đầu tiên.
- Trong
ui/screens/MarsViewModel.kt
, bên trong phương thứcgetMarsPhotos()
, bên trong khốitry
, hãy tìm dòng mã có vai trò thiết lập dữ liệu được truy xuất từ dịch vụ web thànhlistResult
.
// No need to copy, code is already present
try {
val listResult = marsPhotosRepository.getMarsPhotos()
//...
}
- Cập nhật dòng mã này bằng cách thay đổi
listResult
thànhresult
và gán ảnh chụp sao Hoả nằm đầu tiên trong danh sách được truy xuất vào biếnresult
mới. Gán đối tượng ảnh đầu tiên tại chỉ mục0
.
try {
val result = marsPhotosRepository.getMarsPhotos()[0]
//...
}
- Trong dòng mã tiếp theo, hãy cập nhật tham số được truyền đến lệnh gọi hàm
MarsUiState.Success()
thành string trong mã sau. Sử dụng dữ liệu từ thuộc tính mới thay vìlistResult
. Hiển thị URL của hình ảnh đầu tiên trong ảnhresult
.
try {
...
MarsUiState.Success("First Mars image URL: ${result.imgSrc}")
}
Khối try
hoàn chỉnh giờ đây có dạng như mã sau:
marsUiState = try {
val result = marsPhotosRepository.getMarsPhotos()[0]
MarsUiState.Success(
" First Mars image URL : ${result.imgSrc}"
)
}
- Chạy ứng dụng. Thành phần kết hợp
Text
giờ đây hiển thị URL của ảnh chụp sao Hoả nằm đầu tiên trong danh sách. Phần tiếp theo mô tả cách để ứng dụng hiển thị hình ảnh trong URL này.
Thêm thành phần kết hợp AsyncImage
Ở bước này, bạn sẽ thêm hàm có khả năng kết hợp AsyncImage
để tải và hiển thị một ảnh chụp sao Hoả. AsyncImage
là một thành phần kết hợp thực thi yêu cầu hình ảnh một cách không đồng bộ và hiển thị kết quả.
// Example code, no need to copy over
AsyncImage(
model = "https://android.com/sample_image.jpg",
contentDescription = null
)
Đối số model
có thể là giá trị ImageRequest.data
hoặc chính ImageRequest
. Trong ví dụ trước, bạn gán giá trị ImageRequest.data
, tức là URL hình ảnh "https://android.com/sample_image.jpg"
. Đoạn mã ví dụ sau đây cho thấy cách gán chính ImageRequest
cho model
.
// Example code, no need to copy over
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data("https://example.com/image.jpg")
.crossfade(true)
.build(),
placeholder = painterResource(R.drawable.placeholder),
contentDescription = stringResource(R.string.description),
contentScale = ContentScale.Crop,
modifier = Modifier.clip(CircleShape)
)
AsyncImage
hỗ trợ các đối số tương tự như thành phần kết hợp Hình ảnh chuẩn. Ngoài ra, bạn cũng có thể cài đặt trình vẽ placeholder
/error
/fallback
và lệnh gọi lại onLoading
/onSuccess
/onError
. Mã trong ví dụ tải hình ảnh cùng một ảnh cắt theo hình tròn và ảnh động chuyển đổi, rồi đặt một phần giữ chỗ.
contentDescription
đặt văn bản mà các dịch vụ hỗ trợ tiếp cận sử dụng để mô tả nội dung của hình ảnh này.
Thêm thành phần kết hợp AsyncImage
vào mã để hiển thị ảnh chụp sao Hoả đầu tiên được truy xuất.
- Trong
ui/screens/HomeScreen.kt
, hãy thêm một hàm có khả năng kết hợp mới có tên làMarsPhotoCard()
. Hàm này sẽ chứaMarsPhoto
vàModifier
.
@Composable
fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) {
}
- Bên trong hàm có khả năng kết hợp
MarsPhotoCard()
, hãy thêm hàmAsyncImage()
như sau:
import coil.compose.AsyncImage
import coil.request.ImageRequest
import androidx.compose.ui.platform.LocalContext
@Composable
fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) {
AsyncImage(
model = ImageRequest.Builder(context = LocalContext.current)
.data(photo.imgSrc)
.build(),
contentDescription = stringResource(R.string.mars_photo),
modifier = Modifier.fillMaxWidth()
)
}
Trong mã trước, bạn tạo ImageRequest
bằng cách sử dụng và truyền URL hình ảnh (photo.imgSrc
) đến đối số model
. Bạn dùng contentDescription
để đặt văn bản cho trình đọc hỗ trợ tiếp cận.
- Thêm
crossfade(true)
vàoImageRequest
để bật ảnh động chuyển đổi khi yêu cầu hoàn tất thành công.
@Composable
fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) {
AsyncImage(
model = ImageRequest.Builder(context = LocalContext.current)
.data(photo.imgSrc)
.crossfade(true)
.build(),
contentDescription = stringResource(R.string.mars_photo),
modifier = Modifier.fillMaxWidth()
)
}
- Cập nhật thành phần kết hợp
HomeScreen
để hiển thị thành phần kết hợpMarsPhotoCard
thay vì thành phần kết hợpResultScreen
khi yêu cầu hoàn tất thành công. Trong bước tiếp theo, bạn sẽ khắc phục lỗi kiểu dữ liệu không khớp.
@Composable
fun HomeScreen(
marsUiState: MarsUiState,
modifier: Modifier = Modifier
) {
when (marsUiState) {
is MarsUiState.Loading -> LoadingScreen(modifier = modifier.fillMaxSize())
is MarsUiState.Success -> MarsPhotoCard(photo = marsUiState.photos, modifier = modifier.fillMaxSize())
else -> ErrorScreen(modifier = modifier.fillMaxSize())
}
}
- Trong tệp
MarsViewModel.kt
, hãy cập nhật giao diệnMarsUiState
để chấp nhận đối tượngMarsPhoto
thay vìString
.
sealed interface MarsUiState {
data class Success(val photos: MarsPhoto) : MarsUiState
//...
}
- Cập nhật hàm
getMarsPhotos()
để truyền đối tượng ảnh chụp sao Hoả đầu tiên đếnMarsUiState.Success()
. Xoá biếnresult
.
marsUiState = try {
MarsUiState.Success(marsPhotosRepository.getMarsPhotos()[0])
}
- Chạy ứng dụng và xác nhận rằng ứng dụng hiển thị một ảnh chụp sao Hoả.
- Ảnh chụp sao Hoả không lấp đầy toàn bộ màn hình. Để lấp đầy không gian có sẵn trên màn hình, trong
HomeScreen.kt
bên trongAsyncImage
, hãy đặtcontentScale
thànhContentScale.Crop
.
import androidx.compose.ui.layout.ContentScale
@Composable
fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) {
AsyncImage(
model = ImageRequest.Builder(context = LocalContext.current)
.data(photo.imgSrc)
.crossfade(true)
.build(),
contentDescription = stringResource(R.string.mars_photo),
contentScale = ContentScale.Crop,
modifier = modifier,
)
}
- Chạy ứng dụng và xác nhận rằng hình ảnh sẽ lấp đầy màn hình cả chiều ngang và chiều dọc.
Thêm hình ảnh thể hiện lỗi và trạng thái đang tải
Bạn có thể cải thiện trải nghiệm người dùng trong ứng dụng bằng cách hiển thị hình ảnh phần giữ chỗ trong khi tải hình ảnh. Bạn cũng có thể hiển thị hình ảnh lỗi nếu không tải được do sự cố, chẳng hạn như tệp hình ảnh bị thiếu hoặc bị hỏng. Trong phần này, bạn sẽ thêm cả hình ảnh lỗi và hình ảnh giữ chỗ bằng cách sử dụng AsyncImage
.
- Mở
res/drawable/ic_broken_image.xml
và nhấp vào thẻ Design (Thiết kế) hoặc Split (Tách) ở bên phải. Đối với hình ảnh lỗi, hãy dùng biểu tượng hình ảnh bị hỏng có trong thư viện biểu tượng tích hợp. Vectơ vẽ được này sử dụng thuộc tínhandroid:tint
để làm biểu tượng có màu xám.
- Mở
res/drawable/loading_img.xml
. Thành phần có thể vẽ này là một hiệu ứng động làm cho hình ảnh có thể vẽ (loading_img.xml
) xoay xung quanh điểm giữa. (Bạn không thấy ảnh động trong bản xem trước).
- Quay lại tệp
HomeScreen.kt
. Trong thành phần kết hợpMarsPhotoCard
, hãy cập nhật lệnh gọi thànhAsyncImage()
để thêm các thuộc tínherror
vàplaceholder
, như minh hoạ trong đoạn mã dưới đây:
import androidx.compose.ui.res.painterResource
@Composable
fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) {
AsyncImage(
// ...
error = painterResource(R.drawable.ic_broken_image),
placeholder = painterResource(R.drawable.loading_img),
// ...
)
}
Mã này đặt hình ảnh phần giữ chỗ trong quá trình tải để sử dụng trong khi tải (đối tượng có thể vẽ loading_img
). Mã này cũng đặt hình ảnh để sử dụng nếu hình ảnh không tải được (đối tượng có thể vẽ ic_broken_image
).
Thành phần kết hợp MarsPhotoCard
hoàn chỉnh giờ đây có dạng như mã sau:
@Composable
fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) {
AsyncImage(
model = ImageRequest.Builder(context = LocalContext.current)
.data(photo.imgSrc)
.crossfade(true)
.build(),
error = painterResource(R.drawable.ic_broken_image),
placeholder = painterResource(R.drawable.loading_img),
contentDescription = stringResource(R.string.mars_photo),
contentScale = ContentScale.Crop
)
}
- Chạy ứng dụng. Tuỳ thuộc vào tốc độ kết nối mạng, có thể bạn sẽ thấy biểu tượng đang tải trong giây lát khi Coil tải xuống và hiển thị hình ảnh thuộc tính. Tuy nhiên, bạn sẽ không thấy biểu tượng hình ảnh bị hỏng ngay cả khi tắt mạng – bạn sẽ khắc phục vấn đề đó trong nhiệm vụ cuối cùng của lớp học lập trình này.
4. Hiển thị hình ảnh theo bố cục lưới thông qua LazyVerticalGrid
Ứng dụng của bạn đang tải ảnh chụp sao Hoả từ Internet, mục MarsPhoto
đầu tiên trong danh sách. Bạn đã sử dụng URL hình ảnh từ dữ liệu về ảnh chụp sao Hoả đó để điền sẵn dữ liệu vào AsyncImage
. Tuy nhiên, mục tiêu của ứng dụng là hiển thị lưới hình ảnh. Trong nhiệm vụ này, bạn sử dụng LazyVerticalGrid
với trình quản lý bố cục Lưới để hiển thị lưới hình ảnh.
Lưới lazy
Các thành phần kết hợp LazyVerticalGrid và LazyHorizontalGrid hỗ trợ việc hiển thị các mục trong một lưới. Lưới dọc lazy hiển thị các mục thuộc lưới đó trong một vùng chứa có thể cuộn theo chiều dọc, kéo dài qua nhiều cột, còn lưới ngang lazy cũng hoạt động tương tự nhưng theo chiều ngang.
Từ góc độ thiết kế, Bố cục lưới là phù hợp nhất để hiển thị ảnh sao Hoả dưới dạng biểu tượng hoặc hình ảnh.
Tham số columns
trong LazyVerticalGrid
và tham số rows
trong LazyHorizontalGrid
kiểm soát cách các ô được tạo thành cột hoặc hàng. Mã ví dụ dưới đây hiển thị các mục trong một lưới, sử dụng GridCells.Adaptive
để đặt chiều rộng tối thiểu cho mỗi cột là 128.dp
:
// Sample code - No need to copy over
@Composable
fun PhotoGrid(photos: List<Photo>) {
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 150.dp)
) {
items(photos) { photo ->
PhotoItem(photo)
}
}
}
LazyVerticalGrid
cho phép bạn chỉ định chiều rộng của các mục để lưới có thể vừa vặn với nhiều cột nhất có thể. Sau khi tính số lượng cột, lưới này sẽ phân bổ đều chiều rộng còn lại cho các cột. Phương pháp định cỡ thích ứng này đặc biệt hữu ích khi hiển thị các nhóm mục trên nhiều kích thước màn hình.
Trong lớp học lập trình này, để hiển thị ảnh chụp sao Hoả, bạn sử dụng thành phần kết hợp LazyVerticalGrid
với GridCells.Adaptive
, trong đó mỗi cột có chiều rộng là 150.dp
.
Khoá mục
Khi người dùng cuộn qua lưới (LazyRow
trong LazyColumn
), vị trí của mục trong danh sách sẽ thay đổi. Tuy nhiên, do sự thay đổi hướng hoặc nếu các mục được thêm hoặc bị xoá, người dùng có thể mất vị trí cuộn trong hàng. Khoá mục giúp bạn duy trì vị trí cuộn dựa trên khoá.
Bằng cách cung cấp các khoá, bạn giúp Compose xử lý việc sắp xếp lại thứ tự một cách chính xác. Ví dụ: nếu mục của bạn chứa trạng thái đã nhớ, các khoá cài đặt sẽ cho phép Compose di chuyển trạng thái này cùng với mục khi vị trí của mục thay đổi.
Thêm LazyVerticalGrid
Thêm thành phần kết hợp để hiển thị danh sách ảnh sao Hoả theo dạng lưới dọc.
- Trong tệp
HomeScreen.kt
, hãy tạo một hàm có khả năng kết hợp mới có tên làPhotosGridScreen()
. Hàm này sẽ lấy danh sáchMarsPhoto
vàmodifier
làm đối số.
@Composable
fun PhotosGridScreen(
photos: List<MarsPhoto>,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
) {
}
- Bên trong thành phần kết hợp
PhotosGridScreen
, hãy thêmLazyVerticalGrid
với các tham số sau.
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.ui.unit.dp
@Composable
fun PhotosGridScreen(
photos: List<MarsPhoto>,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
) {
LazyVerticalGrid(
columns = GridCells.Adaptive(150.dp),
modifier = modifier.padding(horizontal = 4.dp),
contentPadding = contentPadding,
) {
}
}
- Để thêm danh sách các mục, bên trong hàm lambda
LazyVerticalGrid
, hãy gọi hàmitems()
truyền vào danh sáchMarsPhoto
và khoá mục làphoto.id
.
import androidx.compose.foundation.lazy.grid.items
@Composable
fun PhotosGridScreen(
photos: List<MarsPhoto>,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
) {
LazyVerticalGrid(
// ...
) {
items(items = photos, key = { photo -> photo.id }) {
}
}
}
- Để thêm nội dung mà một mục trong danh sách hiển thị, hãy xác định biểu thức lambda
items
. GọiMarsPhotoCard
, truyền vàophoto
.
items(items = photos, key = { photo -> photo.id }) {
photo -> MarsPhotoCard(photo)
}
- Cập nhật thành phần kết hợp
HomeScreen
để hiển thị thành phần kết hợpPhotosGridScreen
thay vì thành phần kết hợpMarsPhotoCard
khi hoàn tất yêu cầu thành công.
when (marsUiState) {
// ...
is MarsUiState.Success -> PhotosGridScreen(marsUiState.photos, modifier)
// ...
}
- Trong tệp
MarsViewModel.kt
, hãy cập nhật giao diệnMarsUiState
để chấp nhận danh sách đối tượngMarsPhoto
thay vì mộtMarsPhoto
. Thành phần kết hợpPhotosGridScreen
sẽ chấp nhận danh sách đối tượngMarsPhoto
.
sealed interface MarsUiState {
data class Success(val photos: List<MarsPhoto>) : MarsUiState
//...
}
- Trong tệp
MarsViewModel.kt
, hãy cập nhật hàmgetMarsPhotos()
để truyền danh sách đối tượng ảnh chụp sao Hoả vàoMarsUiState.Success()
.
marsUiState = try {
MarsUiState.Success(marsPhotosRepository.getMarsPhotos())
}
- Chạy ứng dụng.
Hãy lưu ý rằng không có khoảng đệm nào quanh mỗi ảnh và tỷ lệ khung hình thay đổi tuỳ theo ảnh. Bạn có thể thêm thành phần kết hợp Card
để khắc phục những vấn đề này.
Thêm thành phần kết hợp thẻ
- Ở tệp
HomeScreen.kt
, trong thành phần kết hợpMarsPhotoCard
, hãy thêmCard
có độ nâng8.dp
xung quanhAsyncImage
. Chỉ định đối sốmodifier
cho thành phần kết hợpCard
.
import androidx.compose.material.Card
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.padding
@Composable
fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) {
Card(
modifier = modifier,
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
) {
AsyncImage(
model = ImageRequest.Builder(context = LocalContext.current)
.data(photo.imgSrc)
.crossfade(true)
.build(),
error = painterResource(R.drawable.ic_broken_image),
placeholder = painterResource(R.drawable.loading_img),
contentDescription = stringResource(R.string.mars_photo),
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth()
)
}
}
- Để chỉnh tỷ lệ khung hình, trong
PhotosGridScreen()
, hãy cập nhật đối tượng sửa đổi choMarsPhotoCard()
.
@Composable
fun PhotosGridScreen(photos: List<MarsPhoto>, modifier: Modifier = Modifier) {
LazyVerticalGrid(
//...
) {
items(items = photos, key = { photo -> photo.id }) { photo ->
MarsPhotoCard(
photo,
modifier = modifier
.padding(4.dp)
.fillMaxWidth()
.aspectRatio(1.5f)
)
}
}
}
- Cập nhật bản xem trước màn hình kết quả để xem trước
PhotosGridScreen()
. Dữ liệu mô phỏng có URL hình ảnh trống.
@Preview(showBackground = true) @Composable fun PhotosGridScreenPreview() { MarsPhotosTheme { val mockData = List(10) { MarsPhoto("$it", "") } PhotosGridScreen(mockData) } }
Vì dữ liệu mô phỏng có URL trống, nên bạn sẽ thấy hình ảnh đang tải trong bản xem trước lưới ảnh.
- Chạy ứng dụng.
- Khi ứng dụng đang chạy, hãy bật Chế độ trên máy bay.
- Cuộn hình ảnh trong trình mô phỏng. Hình ảnh chưa tải sẽ xuất hiện dưới dạng biểu tượng hình ảnh bị hỏng. Đây là hình ảnh có thể vẽ mà bạn đã truyền vào thư viện hình ảnh Coil để hiển thị trong trường hợp có lỗi mạng hoặc không tìm nạp được hình ảnh.
Tốt lắm! Bạn đã mô phỏng lỗi kết nối mạng bằng cách bật Chế độ trên máy bay trong trình mô phỏng hoặc thiết bị.
5. Thêm hành động thử lại
Trong phần này, bạn sẽ thêm một nút hành động để thử lại và truy xuất ảnh khi được nhấp vào.
- Thêm một nút vào màn hình lỗi. Trong tệp
HomeScreen.kt
, hãy cập nhật thành phần kết hợpErrorScreen()
để thêm tham số lambdaretryAction
và nút.
@Composable
fun ErrorScreen(retryAction: () -> Unit, modifier: Modifier = Modifier) {
Column(
// ...
) {
Image(
// ...
)
Text(//...)
Button(onClick = retryAction) {
Text(stringResource(R.string.retry))
}
}
}
Kiểm tra bản xem trước
- Cập nhật thành phần kết hợp
HomeScreen()
để truyền hàm thử lại lambda.
@Composable
fun HomeScreen(
marsUiState: MarsUiState, retryAction: () -> Unit, modifier: Modifier = Modifier
) {
when (marsUiState) {
//...
is MarsUiState.Error -> ErrorScreen(retryAction, modifier = modifier.fillMaxSize())
}
}
- Trong tệp
ui/theme/MarsPhotosApp.kt
, hãy cập nhật lệnh gọi hàmHomeScreen()
để đặt tham số lambdaretryAction
thànhmarsViewModel::getMarsPhotos
. Thao tác này sẽ truy xuất ảnh sao Hoả từ máy chủ.
HomeScreen(
marsUiState = marsViewModel.marsUiState,
retryAction = marsViewModel::getMarsPhotos
)
6. Cập nhật quy trình kiểm thử ViewModel
MarsUiState
và MarsViewModel
hiện chứa danh sách ảnh thay vì một ảnh duy nhất. Ở trạng thái hiện tại, MarsViewModelTest
yêu cầu lớp dữ liệu MarsUiState.Success
chứa một thuộc tính chuỗi. Do đó, quy trình kiểm thử không biên dịch. Bạn cần cập nhật quy trình kiểm thử marsViewModel_getMarsPhotos_verifyMarsUiStateSuccess()
để xác nhận rằng MarsViewModel.marsUiState
bằng với trạng thái Success
chứa danh sách ảnh.
- Mở tệp
rules/MarsViewModelTest.kt
. - Trong quy trình kiểm thử
marsViewModel_getMarsPhotos_verifyMarsUiStateSuccess()
, hãy sửa đổi lệnh gọi hàmassertEquals()
để so sánh trạng tháiSuccess
(truyền danh sách ảnh giả mạo vào thông số ảnh) vớimarsViewModel.marsUiState
.
@Test
fun marsViewModel_getMarsPhotos_verifyMarsUiStateSuccess() =
runTest {
val marsViewModel = MarsViewModel(
marsPhotosRepository = FakeNetworkMarsPhotosRepository()
)
assertEquals(
MarsUiState.Success(FakeDataSource.photosList),
marsViewModel.marsUiState
)
}
Giờ đây, quy trình kiểm thử sẽ biên dịch, chạy và thành công!
7. Lấy mã giải pháp
Để tải xuống mã cho lớp học lập trình đã kết thúc, bạn có thể sử dụng lệnh git này:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos.git
Ngoài ra, bạn có thể tải kho lưu trữ xuống dưới dạng tệp zip, sau đó giải nén và mở trong Android Studio.
Nếu bạn muốn tham khảo đoạn mã giải pháp cho lớp học lập trình này, hãy xem trên GitHub.
8. Kết luận
Chúc mừng bạn đã hoàn thành lớp học lập trình này và xây dựng ứng dụng Mars Photos! Đã đến lúc khoe với gia đình và bạn bè về ứng dụng này cùng với các hình ảnh chụp cảnh thật trên sao Hoả.
Đừng quên chia sẻ thành quả của bạn lên mạng xã hội với hashtag #AndroidBasics!
9. Tìm hiểu thêm
Tài liệu dành cho nhà phát triển Android:
- Danh sách và lưới | Jetpack Compose | Nhà phát triển Android
- Lưới lazy | Jetpack Compose | Nhà phát triển Android
- Tổng quan về ViewModel
Tài liệu khác: