1. Trước khi bắt đầu
Giới thiệu
Cho đến thời điểm này của khoá học, bạn đã nắm rõ kiến thức về cách tạo ứng dụng bằng Compose và hiểu được sơ qua cách tạo ứng dụng bằng XML, thành phần hiển thị, Liên kết thành phần hiển thị và Mảnh. Sau khi tạo ứng dụng bằng thành phần hiển thị, bạn có thể đã nhận ra những điểm thuận tiện khi tạo ứng dụng bằng một giao diện người dùng mang tính khai báo như Compose. Tuy nhiên, trong một số trường hợp, bạn cần sử dụng thành phần hiển thị thay vì Compose. Trong lớp học lập trình này, bạn sẽ tìm hiểu cách dùng Khả năng tương tác với thành phần hiển thị để thêm các thành phần hiển thị vào một ứng dụng Compose hiện đại.
Tại thời điểm chúng tôi viết lớp học lập trình này, các thành phần giao diện người dùng mà bạn sẽ phải tạo chưa có sẵn trong Compose. Đây là cơ hội hoàn hảo để bạn sử dụng Khả năng tương tác với thành phần hiển thị!
Điều kiện tiên quyết:
- Hoàn thành tài liệu môn học trình bày Kiến thức cơ bản về cách tạo ứng dụng Android bằng Compose thông qua lớp học lập trình Tạo ứng dụng Android bằng thành phần hiển thị.
Bạn cần có
- Máy tính có kết nối Internet và Android Studio.
- Một thiết bị hoặc trình mô phỏng
- Mã khởi đầu cho ứng dụng Juice Tracker
Sản phẩm bạn sẽ tạo ra
Trong lớp học lập trình này, bạn sẽ cần tích hợp 3 thành phần hiển thị vào giao diện người dùng Compose để hoàn tất giao diện người dùng cho ứng dụng Juice Tracker; một Danh sách màu sắc (Spinner), thành phần hiển thị (RatingBar) và thành phần hiển thị quảng cáo (AdView). Để tạo các thành phần này, bạn sẽ dùng Khả năng tương tác với thành phần hiển thị (View Interoperability hay viết ngắn gọn là View Interop). Nhờ Khả năng tương tác với Thành phần hiển thị, bạn hoàn toàn có thể thêm các Thành phần hiển thị vào ứng dụng của mình bằng cách gói các Thành phần hiển thị đó trong một Thành phần kết hợp.

Hướng dẫn từng bước về mã
Trong lớp học lập trình này, bạn sẽ thao tác với cùng một ứng dụng JuiceTracker như trong lớp học lập trình Tạo ứng dụng Android bằng thành phần hiển thị và Thêm Compose vào một ứng dụng dựa trên thành phần hiển thị. Phiên bản này chỉ khác biệt ở chỗ là đoạn mã khởi đầu được cung cấp nằm hoàn toàn trong Compose. Ứng dụng hiện thiếu dữ liệu đầu vào về màu sắc và điểm xếp hạng trong bảng hộp thoại nhập thông tin cũng như biểu ngữ quảng cáo ở đầu màn hình danh sách.
Thư mục bottomsheet chứa tất cả các thành phần giao diện người dùng liên quan đến hộp thoại nhập thông tin. Gói này sẽ chứa thành phần giao diện người dùng cho các dữ liệu đầu vào về màu sắc và điểm xếp hạng, khi chúng được tạo.
homescreen chứa các thành phần giao diện người dùng do màn hình chính lưu trữ, bao gồm cả danh sách trong JuiceTracker. Xét cho cùng, gói này phải chứa biểu ngữ quảng cáo, khi biểu ngữ này được tạo.
Các thành phần chính trên giao diện người dùng (chẳng hạn như bảng dưới cùng và danh sách nước ép) được lưu trữ trong tệp JuiceTrackerApp.kt.
2. Lấy đoạn 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-juice-tracker.git $ cd basic-android-kotlin-compose-training-juice-tracker $ git checkout compose-starter
- Trong Android Studio, hãy mở thư mục
basic-android-kotlin-compose-training-juice-tracker. - Mở mã nguồn ứng dụng Juice Tracker trong Android Studio.
3. Cấu hình Gradle
Thêm phần phụ thuộc cho quảng cáo trên Dịch vụ Play vào tệp build.gradle.kts của ứng dụng.
app/build.gradle.kts
android {
...
dependencies {
...
implementation("com.google.android.gms:play-services-ads:22.2.0")
}
}
4. Thiết lập
Thêm giá trị sau vào tệp kê khai Android, ở phía trên thẻ activity, để bật biểu ngữ quảng cáo cho mục đích kiểm thử:
AndroidManifest.xml
...
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713" />
...
5. Hoàn tất hộp thoại nhập thông tin
Trong phần này, bạn sẽ hoàn tất hộp thoại nhập thông tin bằng cách tạo một danh sách màu sắc và thanh điểm xếp hạng. Danh sách màu sắc là thành phần cho phép bạn chọn màu sắc và thanh điểm xếp hạng cho phép bạn chọn điểm xếp hạng cho nước ép đó. Hãy xem giao diện thiết kế dưới đây:


Tạo danh sách màu sắc
Để triển khai một danh sách trong Compose, bạn phải dùng lớp Spinner. Spinner là một thành phần thành phần hiển thị, trái ngược với Thành phần kết hợp, nên phải được triển khai bằng cách sử dụng khả năng tương tác.
- Trong thư mục
bottomsheet, hãy tạo một tệp mới có tênColorSpinnerRow.kt. - Tạo một lớp mới bên trong tệp có tên
SpinnerAdapter. - Trong hàm khởi tạo của
SpinnerAdapter, hãy xác định một tham số callback có tênonColorChange. Tham số này sẽ nhận tham sốInt.SpinnerAdapterxử lý các hàm callback choSpinner.
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit){
}
- Triển khai giao diện
AdapterView.OnItemSelectedListener.
Việc triển khai giao diện này cho phép bạn xác định hành vi nhấp cho danh sách màu sắc. Sau đó, bạn sẽ thiết lập đối tượng chuyển đổi này trong một Thành phần kết hợp.
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
}
- Triển khai các hàm thành phần
AdapterView.OnItemSelectedListener:onItemSelected()vàonNothingSelected().
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
TODO("Not yet implemented")
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
- Sửa đổi hàm
onItemSelected()để gọi hàm callbackonColorChange()sao cho khi bạn chọn một màu sắc, ứng dụng sẽ cập nhật giá trị đã chọn trong giao diện người dùng.
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
onColorChange(position)
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
- Sửa đổi hàm
onNothingSelected()để đặt màu thành0sao cho khi bạn không chọn gì, màu mặc định sẽ là màu đầu tiên, màu đỏ.
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
onColorChange(position)
}
override fun onNothingSelected(parent: AdapterView<*>?) {
onColorChange(0)
}
}
SpinnerAdapter giúp xác định hành vi của danh sách màu sắc thông qua các hàm callback đã được tạo sẵn. Bây giờ, bạn cần tạo nội dung cho danh sách này và điền dữ liệu vào đó.
- Hãy tạo một Thành phần kết hợp mới có tên
ColorSpinnerRowbên trong tệpColorSpinnerRow.kt, nhưng nằm ngoài lớpSpinnerAdapter. - Trong chữ ký phương thức của
ColorSpinnerRow(), hãy thêm một tham sốIntcho vị trí danh sách, một hàm callback nhận tham sốIntvà một đối tượng sửa đổi.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
}
- Bên trong hàm này, hãy tạo một mảng tài nguyên chuỗi về màu nước ép bằng cách sử dụng giá trị enum
JuiceColor. Mảng này chính là nội dung sẽ được đưa vào danh sách màu sắc.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
val juiceColorArray =
JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
}
- Thêm Thành phần kết hợp
InputRow()và truyền tài nguyên chuỗi về màu sắc cho nhãn dữ liệu đầu vào và đối tượng sửa đổi giúp xác định hàng dữ liệu đầu vào màSpinnerxuất hiện.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
val juiceColorArray =
JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
InputRow(inputLabel = stringResource(R.string.color), modifier = modifier) {
}
}
Tiếp theo, bạn sẽ tạo Spinner! Vì Spinner là một lớp thành phần hiển thị, nên bạn phải dùng View interoperability API (API Khả năng tương tác với thành phần hiển thị) trong Compose để gói lớp này vào một Thành phần kết hợp. Để làm điều này, bạn có thể sử dụng Thành phần kết hợp AndroidView.
- Để dùng
Spinnertrong Compose, hãy tạo một Thành phần kết hợpAndroidView()trong nội dung hàm lambdaInputRow. Thành phần kết hợpAndroidView()sẽ tạo một phần tử hoặc hệ phân cấp thành phần hiển thị trong một Thành phần kết hợp.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
val juiceColorArray =
JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
InputRow(inputLabel = stringResource(R.string.color), modifier = modifier) {
AndroidView()
}
}
Thành phần kết hợp AndroidView nhận 3 tham số sau đây:
- Hàm lambda
factory, một hàm tạo ra thành phần hiển thị. - Hàm callback
update, được gọi khi thành phần hiển thị tạo trongfactoryđược mở rộng. modifiercho Thành phần kết hợp.

- Để triển khai
AndroidView, hãy bắt đầu bằng cách truyền một đối tượng sửa đổi và điền dữ liệu về chiều rộng tối đa của màn hình. - Truyền một hàm lambda cho tham số
factory. - Hàm lambda
factorylấyContextlàm tham số. Tạo một lớpSpinnervà truyền ngữ cảnh đó.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
...
InputRow(...) {
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { context ->
Spinner(context)
}
)
}
}
Cũng giống như việc RecyclerView.Adapter cung cấp dữ liệu cho RecyclerView, ArrayAdapter cung cấp dữ liệu cho Spinner. Spinner yêu cầu phải có đối tượng chuyển đổi để lưu giữ mảng màu.
- Thiết lập đối tượng chuyển đổi bằng cách dùng
ArrayAdapter.ArrayAdapteryêu cầu phải có một ngữ cảnh, một bố cục XML và một mảng. Truyềnsimple_spinner_dropdown_itemcho bố cục; bố cục này được cung cấp dưới dạng bố cục mặc định đối với Android.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
...
InputRow(...) {
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { context ->
Spinner(context).apply {
adapter =
ArrayAdapter(
context,
android.R.layout.simple_spinner_dropdown_item,
juiceColorArray
)
}
}
)
}
}
Hàm callback factory trả về một thực thể của thành phần hiển thị được tạo trong đó. update là hàm callback nhận tham số cùng loại do hàm callback factory trả về. Tham số này là một thực thể của thành phần hiển thị và được factory tăng cường. Trong trường hợp này, vì Spinner được tạo trong factory, nên có thể truy cập được thực thể của Spinner trong nội dung hàm lambda update.
- Thêm hàm callback
updateđể truyền mộtspinner. Hãy sử dụng hàm callback được cung cấp trongupdateđể gọi phương thứcsetSelection().
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
...
InputRow(...) {
//...
},
update = { spinner ->
spinner.setSelection(colorSpinnerPosition)
spinner.onItemSelectedListener = SpinnerAdapter(onColorChange)
}
)
}
}
- Dùng
SpinnerAdaptermà bạn đã tạo trước đây để thiết lập một hàm callbackonItemSelectedListener()trongupdate.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
...
InputRow(...) {
AndroidView(
// ...
},
update = { spinner ->
spinner.setSelection(colorSpinnerPosition)
spinner.onItemSelectedListener = SpinnerAdapter(onColorChange)
}
)
}
}
Đoạn mã cho thành phần danh sách màu sắc hiện đã hoàn tất.
- Thêm hàm hiệu dụng sau đây để lấy chỉ mục enum của
JuiceColor. Bạn sẽ dùng chỉ mục này trong bước tiếp theo.
private fun findColorIndex(color: String): Int {
val juiceColor = JuiceColor.valueOf(color)
return JuiceColor.values().indexOf(juiceColor)
}
- Triển khai
ColorSpinnerRowtrong Thành phần kết hợpSheetFormtrong tệpEntryBottomSheet.kt. Đặt danh sách màu sắc phía sau văn bản "Mô tả" và phía trên các nút.
bottomsheet/EntryBottomSheet.kt
...
@Composable
fun SheetForm(
juice: Juice,
onUpdateJuice: (Juice) -> Unit,
onCancel: () -> Unit,
onSubmit: () -> Unit,
modifier: Modifier = Modifier,
) {
...
TextInputRow(
inputLabel = stringResource(R.string.juice_description),
fieldValue = juice.description,
onValueChange = { description -> onUpdateJuice(juice.copy(description = description)) },
modifier = Modifier.fillMaxWidth()
)
ColorSpinnerRow(
colorSpinnerPosition = findColorIndex(juice.color),
onColorChange = { color ->
onUpdateJuice(juice.copy(color = JuiceColor.values()[color].name))
}
)
ButtonRow(
modifier = Modifier
.align(Alignment.End)
.padding(bottom = dimensionResource(R.dimen.padding_medium)),
onCancel = onCancel,
onSubmit = onSubmit,
submitButtonEnabled = juice.name.isNotEmpty()
)
}
}
Tạo dữ liệu đầu vào về điểm xếp hạng
- Tạo một tệp mới trong thư mục
bottomsheetcó tênRatingInputRow.kt. - Trong tệp
RatingInputRow.kt, hãy tạo một Thành phần kết hợp mới có tênRatingInputRow(). - Trong chữ ký phương thức, hãy truyền một
Intcho điểm xếp hạng, một hàm callback có tham sốIntđể xử lý việc thay đổi lựa chọn và một đối tượng sửa đổi.
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
}
- Giống như
ColorSpinnerRow, hãy thêm mộtInputRowvào Thành phần kết hợp có chứaAndroidView, như trong đoạn mã minh hoạ dưới đây.
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
AndroidView(
factory = {},
update = {}
)
}
}
- Trong nội dung hàm lambda
factory, hãy tạo một thực thể của lớpRatingBar. Lớp này cung cấp loại thanh điểm xếp hạng cần có cho giao diện thiết kế này. ĐặtstepSizethành1fđể buộc điểm xếp hạng chỉ được ở dạng số nguyên.
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
AndroidView(
factory = { context ->
RatingBar(context).apply {
stepSize = 1f
}
},
update = {}
)
}
}
Khi thành phần hiển thị được tăng cường, điểm xếp hạng sẽ được đặt. Cần nhớ rằng factory trả về thực thể của RatingBar cho hàm callback update.
- Sử dụng điểm xếp hạng được truyền đến Thành phần kết hợp để đặt điểm xếp hạng cho thực thể
RatingBartrong nội dung hàm lambdaupdate. - Khi đặt một điểm xếp hạng mới, hãy sử dụng hàm callback
RatingBarđể gọi hàm callbackonRatingChange()nhằm cập nhật điểm xếp hạng trong giao diện người dùng.
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
AndroidView(
factory = { context ->
RatingBar(context).apply {
stepSize = 1f
}
},
update = { ratingBar ->
ratingBar.rating = rating.toFloat()
ratingBar.setOnRatingBarChangeListener { _, _, _ ->
onRatingChange(ratingBar.rating.toInt())
}
}
)
}
}
Thành phần kết hợp cho dữ liệu đầu vào về điểm xếp hạng hiện đã hoàn tất.
- Sử dụng thành phần kết hợp
RatingInputRow()trongEntryBottomSheet. Đặt thành phần này phía sau danh sách màu sắc và phía trên các nút.
bottomsheet/EntryBottomSheet.kt
@Composable
fun SheetForm(
juice: Juice,
onUpdateJuice: (Juice) -> Unit,
onCancel: () -> Unit,
onSubmit: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
...
ColorSpinnerRow(
colorSpinnerPosition = findColorIndex(juice.color),
onColorChange = { color ->
onUpdateJuice(juice.copy(color = JuiceColor.values()[color].name))
}
)
RatingInputRow(
rating = juice.rating,
onRatingChange = { rating -> onUpdateJuice(juice.copy(rating = rating)) }
)
ButtonRow(
modifier = Modifier.align(Alignment.CenterHorizontally),
onCancel = onCancel,
onSubmit = onSubmit,
submitButtonEnabled = juice.name.isNotEmpty()
)
}
}
Tạo biểu ngữ quảng cáo
- Trong gói
homescreen, hãy tạo một tệp mới có tênAdBanner.kt. - Trong tệp
AdBanner.kt, hãy tạo một Thành phần kết hợp mới có tênAdBanner().
Không giống như các Thành phần kết hợp bạn đã tạo trước đây, AdBanner không yêu cầu dữ liệu đầu vào. Do đó, bạn không cần phải gói mã này trong một Thành phần kết hợp InputRow. Tuy nhiên, mã này đòi hỏi phải có AndroidView.
- Cố gắng tự tạo biểu ngữ bằng cách sử dụng lớp
AdView. Hãy nhớ đặt kích thước quảng cáo thànhAdSize.BANNERvà mã nhận dạng đơn vị quảng cáo thành"ca-app-pub-3940256099942544/6300978111". - Khi
AdViewđược tăng cường, hãy tải một quảng cáo bằng cách sử dụngAdRequest Builder.
homescreen/AdBanner.kt
@Composable
fun AdBanner(modifier: Modifier = Modifier) {
AndroidView(
modifier = modifier,
factory = { context ->
AdView(context).apply {
setAdSize(AdSize.BANNER)
// Use test ad unit ID
adUnitId = "ca-app-pub-3940256099942544/6300978111"
}
},
update = { adView ->
adView.loadAd(AdRequest.Builder().build())
}
)
}
- Đặt
AdBannertrướcJuiceTrackerListtrongJuiceTrackerApp.JuiceTrackerListđược khai báo trên dòng 83.
ui/JuiceTrackerApp.kt
...
AdBanner(
Modifier
.fillMaxWidth()
.padding(
top = dimensionResource(R.dimen.padding_medium),
bottom = dimensionResource(R.dimen.padding_small)
)
)
JuiceTrackerList(
juices = trackerState,
onDelete = { juice -> juiceTrackerViewModel.deleteJuice(juice) },
onUpdate = { juice ->
juiceTrackerViewModel.updateCurrentJuice(juice)
scope.launch {
bottomSheetScaffoldState.bottomSheetState.expand()
}
},
)
6. Lấy đoạn mã giải pháp
Để tải mã này xuống khi lớp học lập trình đã kết thúc, bạn có thể sử dụng các lệnh git sau:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git $ cd basic-android-kotlin-compose-training-juice-tracker $ git checkout compose-with-views
Ngoài ra, bạn có thể tải kho lưu trữ xuống dưới dạng tệp zip rồi giải nén và mở trong Android Studio.
Nếu muốn xem đoạn mã giải pháp, bạn có thể xem trên GitHub.
7. Tìm hiểu thêm
8. Tổng kết
Mặc dù có thể khoá học này đã kết thúc tại đây, nhưng đó mới chỉ là bước khởi đầu trong hành trình phát triển ứng dụng Android!
Trong khoá học này, bạn đã tìm hiểu cách tạo ứng dụng bằng Jetpack Compose – một bộ công cụ giao diện người dùng hiện đại giúp tạo các ứng dụng Android gốc. Bạn đã tạo các ứng dụng có danh sách, một hoặc nhiều màn hình và di chuyển giữa các màn hình trong suốt khoá học. Bạn đã tìm hiểu cách tạo các ứng dụng mang tính tương tác, giúp ứng dụng phản hồi với hoạt động đầu vào của người dùng và cập nhật giao diện người dùng. Bạn đã áp dụng Material Design và sử dụng các màu sắc, hình khối và kiểu chữ để tạo giao diện cho ứng dụng. Bạn cũng sử dụng Jetpack và những thư viện khác của bên thứ ba để lên lịch cho các tác vụ, truy xuất dữ liệu qua máy chủ từ xa, lưu giữ dữ liệu trên máy, v.v.
Sau khi hoàn thành khoá học này, bạn không chỉ hiểu rõ cách tạo một ứng dụng vừa đẹp mắt vừa có khả năng thích ứng bằng Jetpack Compose mà còn được trang bị kiến thức và kỹ năng cần thiết để tạo các ứng dụng Android hữu ích, dễ bảo trì và bắt mắt. Những kiến thức nền tảng này sẽ giúp bạn tiếp tục học hỏi và xây dựng các kỹ năng trong lĩnh vực Phát triển Android hiện đại và trong Compose.
Cảm ơn bạn đã tham gia và hoàn thành khoá học này! Bạn có thể tiếp tục tìm hiểu và mở rộng kỹ năng của mình thông qua các tài nguyên bổ sung như Tài liệu dành cho nhà phát triển Android, Khoá học Jetpack Compose dành cho nhà phát triển Android, Cấu trúc ứng dụng Android hiện đại, Blog dành cho nhà phát triển Android, các lớp học lập trình khác cũng như những dự án mẫu.
Cuối cùng, đừng quên chia sẻ những ứng dụng mà bạn đã tạo lên mạng xã hội. Hãy dùng hashtag #AndroidBasics để chúng tôi và những người khác trong cộng đồng nhà phát triển Android cũng có thể theo dõi hành trình học tập của bạn!
Chúc bạn luôn thành công!!