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 khung hiển thị, bạn hoàn toàn có thể thêm các Khung hiển thị vào ứng dụng của mình bằng cách gói các Khung 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ã ứ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
.SpinnerAdapter
xử 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ành0
sao 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
ColorSpinnerRow
bê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ốInt
cho vị trí danh sách, một hàm callback nhận tham sốInt
và 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àSpinner
xuấ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
Spinner
trong 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. modifier
cho 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
factory
lấyContext
làm tham số. Tạo một lớpSpinner
và 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
.ArrayAdapter
yê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_item
cho 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
SpinnerAdapter
mà 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
ColorSpinnerRow
trong Thành phần kết hợpSheetForm
trong 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
bottomsheet
có 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
Int
cho đ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ộtInputRow
và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. ĐặtstepSize
thà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ể
RatingBar
trong 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.BANNER
và 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
AdBanner
trướcJuiceTrackerList
trongJuiceTrackerApp
.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 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 bạn muốn xem mã giải pháp, hãy xem mã đó 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!!