Android Developer Roadmap: Giới thiệu Jetpack Compose – Bộ công cụ UI hiện đại cho Android

Chào mừng các bạn quay trở lại với loạt bài viết Android Developer Roadmap – Lộ trình học Lập trình viên Android 2025! Chúng ta đã cùng nhau đi qua nhiều chặng đường quan trọng, từ việc thiết lập môi trường phát triển, học Kotlin (ngôn ngữ chủ đạo mà chúng ta đã thảo luận nên chọn cho năm 2025), nắm vững OOP, cấu trúc dữ liệu và giải thuật, cho đến việc tạo ra ứng dụng “Hello World” đầu tiên và làm quen với các khái niệm cốt lõi như vòng đời Activity, Backstack, Intents, Intent Filter, Services, Layouts truyền thống, RecyclerView, và các View cơ bản. Chúng ta cũng đã tìm hiểu về Fragments, Dialogs, Bottom Sheets, Drawers và cả Animations để làm giao diện sinh động hơn.

Giờ đây, khi bạn đã có nền tảng vững chắc về cách các thành phần Android hoạt động và cách xây dựng giao diện người dùng theo cách truyền thống (XML View System), đã đến lúc chúng ta khám phá một phương pháp xây dựng UI mới, hiện đại và mạnh mẽ hơn rất nhiều: Jetpack Compose.

Trong bài viết này, chúng ta sẽ cùng tìm hiểu Jetpack Compose là gì, tại sao nó lại quan trọng và làm thế nào để bắt đầu với nó. Đây là một bước ngoặt lớn trong phát triển Android UI và là kỹ năng không thể thiếu trên lộ trình của một nhà phát triển Android hiện đại.

Jetpack Compose là gì? Tại sao lại cần một bộ công cụ UI mới?

Trong nhiều năm, cách chính để xây dựng giao diện người dùng trên Android là sử dụng hệ thống View dựa trên XML. Bạn định nghĩa cấu trúc UI trong các tệp XML layout, sau đó trong mã nguồn Java hoặc Kotlin, bạn “phồng” (inflate) layout đó, tìm các View bằng ID (như cách chúng ta làm với findViewById, mặc dù các thư viện sau này như View Binding hay Data Binding đã cải thiện phần nào), và cập nhật trạng thái của View bằng cách gọi các phương thức setter.

Cách tiếp cận này, được gọi là lập trình UI theo kiểu mệnh lệnh (Imperative UI), có những hạn chế nhất định:

  • Code dài dòng và lặp lại (Boilerplate): Việc kết nối XML với code, quản lý trạng thái của View (hiển thị/ẩn, enabled/disabled, text), và cập nhật UI khi dữ liệu thay đổi thường đòi hỏi nhiều mã lặp đi lặp lại.
  • Quản lý trạng thái phức tạp: Theo dõi và cập nhật UI một cách chính xác khi dữ liệu thay đổi có thể dẫn đến lỗi, đặc biệt trong các giao diện phức tạp hoặc có nhiều thay đổi đồng thời. Bạn phải tự quản lý việc cập nhật từng thuộc tính của từng View.
  • Hiệu năng: Việc inflate XML và tìm View có thể tốn kém tài nguyên, đặc biệt với các danh sách động (RecyclerView đã giải quyết vấn đề này cho danh sách lớn, nhưng bản thân nó cũng có độ phức tạp nhất định).
  • Khó phát triển và bảo trì: Khi UI trở nên phức tạp, việc hiểu luồng cập nhật trạng thái và debug các vấn đề về giao diện có thể rất khó khăn.

Để giải quyết những vấn đề này và mang đến một cách tiếp cận hiện đại, hiệu quả hơn, Google đã giới thiệu Jetpack Compose. Jetpack Compose là bộ công cụ UI (UI toolkit) hiện đại được xây dựng trên Kotlin, cho phép bạn xây dựng giao diện người dùng Android một cách tuyên bố (Declarative UI).

Thay vì mô tả cách cập nhật UI (tìm View X, đặt text Y, đổi màu Z), với Compose, bạn chỉ cần mô tả UI trông như thế nào tại một thời điểm dựa trên trạng thái dữ liệu hiện tại. Compose sẽ tự động lo việc cập nhật UI khi dữ liệu thay đổi. Nghe có vẻ giống với các framework UI hiện đại khác như React, Flutter, SwiftUI, và đúng là Compose chia sẻ nhiều ý tưởng cốt lõi với chúng.

Lập trình UI Tuyên bố (Declarative UI) là gì?

Hãy hình dung bạn muốn làm một tách cà phê:

  • Mệnh lệnh (Imperative): Bạn đưa ra một danh sách các bước chi tiết: “Lấy cốc”, “Cho hai thìa cà phê vào cốc”, “Đổ nước nóng vào cốc”, “Thêm đường nếu muốn”, “Thêm sữa nếu muốn”, “Khuấy đều”. Nếu bạn muốn thay đổi thành trà, bạn phải đưa ra một danh sách các bước hoàn toàn khác.
  • Tuyên bố (Declarative): Bạn chỉ cần nói: “Tôi muốn một tách cà phê (hoặc trà) với đường và sữa”. Bạn chỉ mô tả kết quả mong muốn, không cần biết các bước chi tiết để thực hiện nó. Hệ thống (người pha chế) sẽ tự biết cách làm dựa trên yêu cầu của bạn.

Trong phát triển UI, Imperative UI (như hệ thống View truyền thống) yêu cầu bạn quản lý vòng đời của View và tự cập nhật chúng thủ công. Declarative UI (như Compose) cho phép bạn mô tả giao diện cho từng trạng thái dữ liệu cụ thể. Khi trạng thái dữ liệu thay đổi, bạn không cần tự mình tìm View và cập nhật; thay vào đó, bạn chỉ cần cung cấp dữ liệu mới và Compose sẽ tự động “vẽ lại” (recompose) các phần UI cần thiết để phản ánh trạng thái mới đó.

Các Khái niệm Cốt lõi của Jetpack Compose

Để làm quen với Compose, bạn cần hiểu các khái niệm cơ bản sau:

Composable Functions (@Composable)

Đơn vị xây dựng cơ bản trong Compose là Composable function. Đây là các hàm đặc biệt được đánh dấu bằng annotation @Composable. Thay vì trả về một View, một Composable function mô tả một phần giao diện người dùng.

Một Composable function có thể nhận dữ liệu làm tham số và dựa vào dữ liệu đó để “phát ra” (emit) các thành phần UI. Nó không có vòng đời phức tạp như Activity hay Fragment, chỉ đơn giản là một hàm mô tả UI.

@Composable
fun Greeting(name: String) {
    Text(text = "Xin chào $name!")
}

Trong ví dụ trên, Greeting là một Composable function nhận một chuỗi name và hiển thị một đoạn văn bản sử dụng Composable Text tích hợp sẵn. Chú ý rằng nó không trả về gì cả (Unit trong Kotlin), vai trò của nó là mô tả UI.

Bạn có thể lồng các Composable function vào nhau để xây dựng cấu trúc UI phức tạp hơn:

@Composable
fun UserProfile(userName: String, bio: String) {
    Column { // Column là một Composable layout
        Greeting(name = userName) // Gọi Composable khác
        Text(text = bio)
    }
}

State (Trạng thái)

Giao diện người dùng thường thay đổi theo thời gian để phản ánh dữ liệu hiện tại của ứng dụng (ví dụ: nội dung văn bản trong EditText, trạng thái check của Checkbox, danh sách item trong RecyclerView). Dữ liệu này được gọi là State (Trạng thái).

Trong hệ thống View truyền thống, bạn thường lưu trữ trạng thái trong các biến trong Activity hoặc Fragment và cập nhật View thủ công khi trạng thái thay đổi.

Trong Compose, khi trạng thái (dữ liệu) mà một Composable function dựa vào để mô tả UI thay đổi, Composable đó sẽ được “vẽ lại” (recompose) với dữ liệu mới. Compose cung cấp các cơ chế để bạn khai báo và quản lý trạng thái một cách hiệu quả.

Recomposition (Tái biên dịch/Vẽ lại)

Recomposition là quá trình Compose gọi lại Composable function của bạn khi dữ liệu (state) mà nó đọc thay đổi. Đây là cách Compose cập nhật UI để phản ánh trạng thái mới.

Điểm đặc biệt của Recomposition là nó rất thông minh. Compose chỉ gọi lại các Composable function bị ảnh hưởng bởi sự thay đổi của trạng thái, không phải toàn bộ cây UI. Quá trình này rất nhanh và hiệu quả.

Khi bạn khai báo một biến trạng thái trong Composable bằng các hàm như remembermutableStateOf (chúng ta sẽ nói thêm bên dưới), Compose sẽ theo dõi biến đó. Khi giá trị của biến thay đổi, Compose sẽ lên lịch Recomposition cho các Composable đang “quan sát” (read) giá trị đó.

Xây dựng Giao diện với các Composable cơ bản

Compose cung cấp một bộ sưu tập phong phú các Composable tích hợp sẵn để xây dựng UI. Dưới đây là một vài ví dụ phổ biến:

import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun SimpleUIExample() {
    // Column sắp xếp các phần tử con theo chiều dọc
    Column(
        modifier = Modifier
            .fillMaxSize() // Chiếm toàn bộ không gian
            .padding(16.dp), // Thêm padding
        horizontalAlignment = Alignment.CenterHorizontally, // Căn giữa theo chiều ngang
        verticalArrangement = Arrangement.Center // Căn giữa theo chiều dọc
    ) {
        // Text: Hiển thị văn bản
        Text(
            text = "Chào mừng đến với Jetpack Compose!",
            style = MaterialTheme.typography.h5 // Áp dụng style văn bản
        )

        Spacer(modifier = Modifier.height(16.dp)) // Tạo khoảng trống

        // Button: Nút bấm
        Button(onClick = {
            // Xử lý sự kiện khi nút được bấm
            println("Nút đã được bấm!")
        }) {
            Text("Bấm vào đây")
        }

        Spacer(modifier = Modifier.height(16.dp))

        // EditText (TextField trong Compose)
        var inputText by remember { mutableStateOf("") } // Khai báo trạng thái cho input text
        TextField(
            value = inputText,
            onValueChange = { inputText = it }, // Cập nhật trạng thái khi text thay đổi
            label = { Text("Nhập tên của bạn") }
        )

        Spacer(modifier = Modifier.height(16.dp))

        // Hiển thị văn bản dựa trên input
        Text(text = "Xin chào: $inputText")
    }
}

Trong ví dụ trên, bạn thấy:

  • Chúng ta sử dụng các Composable như Column, Text, Button, Spacer, TextField.
  • Modifier là một khái niệm quan trọng trong Compose, dùng để thêm các thuộc tính như kích thước, padding, căn chỉnh, background, v.v., cho Composable. Chúng ta sẽ tìm hiểu sâu hơn về Modifier sau.
  • Chúng ta sử dụng remember { mutableStateOf("") } để tạo và quản lý trạng thái của TextField.
  • Khi người dùng nhập liệu vào TextField, hàm onValueChange được gọi, cập nhật biến inputText.
  • Việc cập nhật inputText (là một trạng thái) sẽ kích hoạt Recomposition. Compose sẽ nhận ra rằng Composable Text(text = "Xin chào: $inputText") đọc giá trị của inputText, nên chỉ Composable Text này (và các Composable cha nếu cần) sẽ được vẽ lại với giá trị inputText mới, cập nhật dòng chữ “Xin chào: …”.

Quản lý State trong Compose với remember và mutableStateOf

Như đã thấy ở ví dụ trên, quản lý trạng thái là trung tâm của lập trình UI tuyên bố. Compose cung cấp các cách để bạn khai báo “trạng thái” trong Composable. Hai hàm thường dùng nhất là remembermutableStateOf.

  • mutableStateOf tạo ra một đối tượng MutableState<T> có thể quan sát được. Khi giá trị của đối tượng này thay đổi, Composable đọc nó sẽ được Recompose.
  • remember là một Composable function, nó “ghi nhớ” một giá trị qua các lần Recomposition. Nếu bạn không dùng remember, mỗi lần Composable được gọi lại (Recompose), biến trạng thái sẽ được khởi tạo lại về giá trị ban đầu.

Kết hợp cả hai, remember { mutableStateOf(...) }, chúng ta tạo ra một biến trạng thái có thể thay đổi và giữ nguyên giá trị qua các lần Recomposition.

Sử dụng cú pháp ủy quyền thuộc tính (property delegation) by của Kotlin giúp làm cho việc làm việc với MutableState gọn gàng hơn:

var count by remember { mutableStateOf(0) }
// Thay vì:
// val countState = remember { mutableStateOf(0) }
// val count = countState.value
// để đọc giá trị: countState.value = newValue để cập nhật
// Với `by`, bạn chỉ cần dùng `count` như một biến thông thường
// để đọc: count
// để cập nhật: count = newValue

Đây là một ví dụ đơn giản về bộ đếm sử dụng State:

@Composable
fun Counter() {
    // State được nhớ qua các lần Recomposition
    var count by remember { mutableStateOf(0) }

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text("Số lần bấm: $count", style = MaterialTheme.typography.h6)
        Spacer(modifier = Modifier.height(8.dp))
        Button(onClick = {
            count++ // Cập nhật trạng thái -> kích hoạt Recomposition
        }) {
            Text("Tăng")
        }
    }
}

Khi nút “Tăng” được bấm, biến count thay đổi giá trị. Điều này khiến Compose nhận ra rằng Composable Text đang hiển thị giá trị của count cần được cập nhật. Compose sẽ Recompose phần UI đó, hiển thị giá trị count mới.

Layouts trong Jetpack Compose

Trong hệ thống View truyền thống, chúng ta có LinearLayout, RelativeLayout, ConstraintLayout để bố trí các phần tử UI. Compose cũng cung cấp các Composable layout tương tự nhưng với cách tiếp cận khác.

  • Column: Sắp xếp các Composable con theo chiều dọc.
  • Row: Sắp xếp các Composable con theo chiều ngang.
  • Box: Xếp chồng các Composable con lên nhau (như FrameLayout).

Các Composable layout này nhận các Composable khác làm nội dung (content) của chúng và sử dụng các đối số như horizontalAlignment, verticalArrangement (cho Column/Row) hoặc các Modifier để điều chỉnh vị trí và kích thước của các phần tử con.

Đối với các layout phức tạp hơn, Compose cung cấp các Modifier mạnh mẽ và khả năng tạo layout tùy chỉnh dễ dàng hơn so với hệ thống View truyền thống.

Tích hợp Jetpack Compose với Hệ thống View hiện có

Một trong những lợi ích lớn của Jetpack Compose là bạn không cần phải viết lại toàn bộ ứng dụng của mình ngay lập tức. Compose được thiết kế để có thể tích hợp dần dần vào các ứng dụng hiện có sử dụng hệ thống View truyền thống.

Bạn có thể sử dụng ComposeView trong XML layout của mình để nhúng các Composable vào đó. Ngược lại, bạn cũng có thể nhúng các View truyền thống vào trong cây Composable của mình bằng AndroidView.

Điều này cho phép bạn bắt đầu học và sử dụng Compose cho các màn hình hoặc tính năng mới, trong khi vẫn duy trì và dần dần di chuyển các phần cũ sang Compose khi có thời gian.

Tại sao Jetpack Compose lại quan trọng trên Lộ trình của bạn?

Học Jetpack Compose không chỉ là học một công cụ mới, đó là học một cách tư duy mới về xây dựng giao diện người dùng. Đây là tương lai của phát triển UI trên Android, và có nhiều lý do bạn nên đầu tư thời gian vào nó ngay bây giờ:

  • Được Google ủng hộ mạnh mẽ: Compose là công nghệ được Google tập trung phát triển cho Android UI.
  • Giảm thiểu mã nguồn: Code Compose thường ngắn gọn, dễ đọc và dễ hiểu hơn so với việc quản lý XML và mã logic tương tác.
  • Tăng tốc độ phát triển: Khả năng xem trước trực tiếp (Preview) và cách tư duy tuyên bố giúp bạn xây dựng UI nhanh hơn.
  • Hiệu năng tốt: Recomposition thông minh giúp cập nhật UI hiệu quả.
  • Sử dụng hoàn toàn bằng Kotlin: Tận dụng sức mạnh và tính năng của ngôn ngữ Kotlin.
  • Cộng đồng đang phát triển: Ngày càng có nhiều tài liệu, thư viện và dự án sử dụng Compose.

Mặc dù hệ thống View truyền thống vẫn tồn tại và hoạt động tốt, nhưng Compose đang nhanh chóng trở thành lựa chọn mặc định cho các dự án mới và là một kỹ năng rất được săn đón trên thị trường việc làm.

Thiết lập Jetpack Compose trong dự án

Để bắt đầu sử dụng Jetpack Compose trong dự án Android Studio của bạn, bạn cần đảm bảo đang sử dụng phiên bản Android Studio mới nhất (thường là phiên bản Preview hoặc Beta hỗ trợ tốt nhất các tính năng mới nhất của Compose). Sau đó, bạn cần cấu hình file build.gradle (app level):

// build.gradle (app level)

android {
    // ... cấu hình khác

    buildFeatures {
        compose true // Bật Jetpack Compose
    }

    composeOptions {
        // Chỉ định phiên bản Kotlin Compiler Extension
        // Cần khớp với phiên bản Kotlin bạn đang sử dụng
        kotlinCompilerExtensionVersion '1.5.10' // Thay bằng phiên bản mới nhất phù hợp
    }

    // ... cấu hình khác
}

dependencies {
    // ... các dependencies khác

    // BOM (Bill of Materials) giúp quản lý các phiên bản Compose
    // Bạn chỉ cần chỉ định phiên bản của BOM, các thư viện Compose khác
    // sẽ tự động sử dụng phiên bản tương thích
    val composeBom = platform('androidx.compose:compose-bom:2023.08.00') // Thay bằng phiên bản BOM mới nhất
    implementation(composeBom)

    // Các thư viện Compose cốt lõi
    implementation("androidx.compose.ui:ui") // Core UI framework
    implementation("androidx.compose.material:material") // Material Design components
    implementation("androidx.compose.ui:ui-tooling-preview") // Preview trong Android Studio
    implementation("androidx.activity:activity-compose:1.7.2") // Tích hợp Activity với Compose

    // Optional: Thêm các thư viện Material 3, Tooling, Test, v.v. nếu cần
    implementation("androidx.compose.material3:material3")

    // Debug and testing
    debugImplementation("androidx.compose.ui:ui-tooling")
    debugImplementation("androidx.compose.ui:ui-test-manifest")
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")

}

Sau khi thêm các dependency này và Sync Project, bạn đã sẵn sàng viết Composable functions.

Điểm vào (entry point) phổ biến nhất để hiển thị UI Compose là trong một Activity. Bạn sử dụng hàm setContent thay vì setContentView:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // Đây là nơi bạn đặt Composable function gốc của màn hình
            // Ví dụ:
            SimpleUIExample()
            // Hoặc bất kỳ Composable nào khác
        }
    }
}

Tất nhiên, bạn có thể tạo các Composable function ở bất kỳ đâu trong project và gọi chúng từ setContent hoặc từ các Composable khác.

So sánh Jetpack Compose và Hệ thống View truyền thống

Để tóm tắt lại, đây là bảng so sánh ngắn gọn giữa hai phương pháp:

Đặc điểm Hệ thống View truyền thống (XML + Kode) Jetpack Compose (Kotlin)
Kiểu lập trình UI Mệnh lệnh (Imperative) – Bạn mô tả các bước để cập nhật UI. Tuyên bố (Declarative) – Bạn mô tả UI trông như thế nào dựa trên trạng thái.
Ngôn ngữ XML cho layout, Kotlin/Java cho logic. Hoàn toàn bằng Kotlin.
Quản lý trạng thái Thủ công, dễ phát sinh lỗi và code lặp lại. Được tích hợp sẵn, dễ dàng theo dõi và cập nhật.
Hiệu năng cập nhật UI Dựa trên việc tìm và thay đổi thuộc tính của View. RecyclerView cải thiện cho danh sách. Recomposition thông minh, chỉ cập nhật các phần cần thiết.
Khả năng tích hợp Tốt với code Java/Kotlin hiện có. Tốt, có thể tích hợp dần vào project hiện có.
Tốc độ phát triển Có thể chậm hơn do boilerplate và quản lý trạng thái thủ công. Thường nhanh hơn nhờ code gọn gàng và Preview.
Xem trước (Preview) Layout Preview. Composable Preview mạnh mẽ, có thể xem trước nhiều trạng thái.

Lời kết

Jetpack Compose là một bước tiến lớn và là tương lai của việc xây dựng giao diện người dùng trên Android. Việc học và làm chủ Compose sẽ mở ra nhiều cơ hội và giúp bạn trở thành một nhà phát triển Android hiện đại, hiệu quả hơn.

Trong khuôn khổ bài viết giới thiệu này, chúng ta chỉ mới chạm vào bề mặt của Compose. Còn rất nhiều điều thú vị để khám phá như Modifiers, State Hoisting, Architecture Patterns với Compose (như MVVM), Navigation với Compose, Testing Composable, Animation, và nhiều hơn nữa.

Hãy dành thời gian thực hành với các ví dụ đơn giản, thử tạo các màn hình cơ bản của riêng bạn. Đừng ngần ngại tham khảo tài liệu chính thức của Google về Jetpack Compose, đó là nguồn tài nguyên rất giá trị.

Việc chuyển đổi từ hệ thống View truyền thống sang Compose có thể cần một chút thời gian và nỗ lực để thay đổi cách tư duy, nhưng lợi ích mà nó mang lại là rất đáng giá. Jetpack Compose chắc chắn là một điểm dừng quan trọng trên Lộ trình học Lập trình viên Android 2025 của bạn.

Trong các bài viết tiếp theo của series, chúng ta sẽ tiếp tục khám phá sâu hơn về Compose và các khía cạnh khác của phát triển Android. Hãy cùng nhau tiếp tục hành trình này nhé!

Hẹn gặp lại trong bài viết tiếp theo!

Chỉ mục