Khả năng tương tác với khung hiển thị trong Compose

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:

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.

a02177f6b6277edc.png afc4551fde8c3113.png 5dab7f58a3649c04.png

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ị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
  1. Trong Android Studio, hãy mở thư mục basic-android-kotlin-compose-training-juice-tracker.
  2. 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:

Danh sách màu sắc với nhiều màu sắc được liệt kê

Thanh xếp hạng với 4/5 sao được chọn

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.

  1. Trong thư mục bottomsheet, hãy tạo một tệp mới có tên ColorSpinnerRow.kt.
  2. Tạo một lớp mới bên trong tệp có tên SpinnerAdapter.
  3. Trong hàm khởi tạo của SpinnerAdapter, hãy xác định một tham số callback có tên onColorChange. Tham số này sẽ nhận tham số Int. SpinnerAdapter xử lý các hàm callback cho Spinner.

bottomsheet/ColorSpinnerRow.kt

class SpinnerAdapter(val onColorChange: (Int) -> Unit){
}
  1. 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 {
}
  1. Triển khai các hàm thành phần AdapterView.OnItemSelectedListener: onItemSelected()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")
    }
}
  1. Sửa đổi hàm onItemSelected() để gọi hàm callback onColorChange() 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")
    }
}
  1. Sửa đổi hàm onNothingSelected() để đặt màu thành 0 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 đó.

  1. Hãy tạo một Thành phần kết hợp mới có tên ColorSpinnerRow bên trong tệp ColorSpinnerRow.kt, nhưng nằm ngoài lớp SpinnerAdapter.
  2. 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
) {
}
  1. 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) }

}
  1. 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.

  1. Để dùng Spinner trong Compose, hãy tạo một Thành phần kết hợp AndroidView() trong nội dung hàm lambda InputRow. Thành phần kết hợp AndroidView() 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 trong factory được mở rộng.
  • modifier cho Thành phần kết hợp.

3bb9f605719b173.png

  1. Để 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.
  2. Truyền một hàm lambda cho tham số factory.
  3. Hàm lambda factory lấy Context làm tham số. Tạo một lớp Spinner 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.

  1. 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ền simple_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.

  1. Thêm hàm callback update để truyền một spinner. Hãy sử dụng hàm callback được cung cấp trong update để gọi phương thức setSelection().

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   ...
   InputRow(...) {
      //...
         },
         update = { spinner ->
             spinner.setSelection(colorSpinnerPosition)
             spinner.onItemSelectedListener = SpinnerAdapter(onColorChange)
         }
      )
   }
}
  1. Dùng SpinnerAdapter mà bạn đã tạo trước đây để thiết lập một hàm callback onItemSelectedListener() trong update.

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.

  1. 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)
}
  1. Triển khai ColorSpinnerRow trong Thành phần kết hợp SheetForm trong tệp EntryBottomSheet.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

  1. Tạo một tệp mới trong thư mục bottomsheet có tên RatingInputRow.kt.
  2. Trong tệp RatingInputRow.kt, hãy tạo một Thành phần kết hợp mới có tên RatingInputRow().
  3. 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){
}
  1. Giống như ColorSpinnerRow, hãy thêm một InputRow vào Thành phần kết hợp có chứa AndroidView, 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 = {}
        )
    }
}
  1. Trong nội dung hàm lambda factory, hãy tạo một thực thể của lớp RatingBar. 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. Đặt stepSize thành 1f để 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.

  1. 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 lambda update.
  2. 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 callback onRatingChange() 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.

  1. Sử dụng thành phần kết hợp RatingInputRow() trong EntryBottomSheet. Đặ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

  1. Trong gói homescreen, hãy tạo một tệp mới có tên AdBanner.kt.
  2. Trong tệp AdBanner.kt, hãy tạo một Thành phần kết hợp mới có tên AdBanner().

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.

  1. 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ành AdSize.BANNER và mã nhận dạng đơn vị quảng cáo thành "ca-app-pub-3940256099942544/6300978111".
  2. Khi AdView được tăng cường, hãy tải một quảng cáo bằng cách sử dụng AdRequest 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())
        }
    )
}
  1. Đặt AdBanner trước JuiceTrackerList trong JuiceTrackerApp. 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!!