Trong lập trình Android hiện đại, Jetpack Compose đã trở thành công nghệ quan trọng giúp đơn giản hóa việc xây dựng giao diện người dùng (UI) bằng cách áp dụng Reactive programming. Một trong những khái niệm cốt lõi mà chúng ta cần hiểu khi làm việc với Jetpack Compose chính là State.
State trong Jetpack Compose giúp quản lý dữ liệu và đảm bảo giao diện được cập nhật tự động khi dữ liệu thay đổi. Điều này loại bỏ nhu cầu gọi thủ công các phương thức như setText()
hay setVisibility()
như trong cách tiếp cận XML truyền thống. Tuy nhiên, để sử dụng state một cách hiệu quả, ta cần hiểu rõ về stateless composables, stateful composables, cũng như các phương pháp quản lý state như State Hoisting, ViewModel, và các công nghệ liên quan như LiveData, Flow, hay RxJava.
Trong bài viết này, chúng ta sẽ cùng khám phá sâu về State trong Jetpack Compose, hiểu cách hoạt động, các quy tắc quan trọng, cũng như áp dụng nó một cách tối ưu trong các ứng dụng Android.
State trong Jetpack Compose là gì?
Hiểu đơn giản thì State là một giá trị dữ liệu liên quan đến việc cập nhật UI. Mỗi khi giá trị của state thay đổi, Jetpack Compose sẽ tự động cập nhật lại UI mà liên quan đến state đó.
Giá trị của state có thể thuộc bất kỳ kiểu dữ liệu nào. Có thể là một giá trị thuộc các kiểu dữ liệu cơ bản như Boolean
hoặc String
,… hoặc thậm chí có thể là một class chứa toàn bộ thông tin cho một màn hình được hiển thị.
Các loại State trong Compose
- Local State: Đây là state được khai báo và sử dụng chỉ trong một hàm composable và không chia sẻ nó ra bên ngoài. Ví dụ như là chúng ta có một thành phần giao diện (composable function) chứa 1 nút bấm có tác dụng ẩn/hiện một đoạn text ngay dưới nút bấm đó. Ở đây ta sẽ khai báo một biến state quản lý trạng thái ẩn hiện của đoạn text ngay trong hàm này.
@Composable fun UpdateTextUI() { val textUIState = remember { mutableStateOf(false) } Column(modifier = Modifier.fillMaxSize()) { Button( onClick = { textUIState.value = !textUIState.value }, ) { Text(text = "Button") } if (textUIState.value) { Text( text = "Data Text", modifier = Modifier.fillMaxWidth() ) } } }
- Hoisted State: Thì ngược lại, State được đẩy ra bên ngoài của hàm composable. Hiểu đơn giản là loại bỏ state khỏi composable và truyền nó qua tham số. Điều này làm cho việc quản lý giữa các phần khác nhau của UI trở nên dễ dàng hơn. Hoisted state hiệu quả khi mà có nhiều thành phần composable cần được quản lý bởi cùng một state.
// Root Composable Function @Composable fun MyApp() { // State Hoisting: State is managed at a higher level var count by remember { mutableStateOf(0) } // Passing state and update logic down to composables CounterScreen(count = count, onIncrement = { count++ }) } // Composable Function that Displays Counter UI @Composable fun CounterScreen(count: Int, onIncrement: () -> Unit) { Column( modifier = Modifier .fillMaxSize() .padding(16.dp), verticalArrangement = Arrangement.Center ) { // Display the current count CounterText(count = count) CounterButton(onClick = onIncrement) } } // Reusable Composable to Display Count @Composable fun CounterText(count: Int) { Text(text = "You clicked $count times") } // Reusable Composable for Button @Composable fun CounterButton(onClick: () -> Unit) { Button(onClick = onClick) { Text(text = "Click Me") } }
Cách tạo một đối tượng State trong Jetpack Compose?
Để tạo một State đơn giản ta có thể khai báo giống như sau
var enabled by remember { mutableStateOf(true) }
mutableStateOf(true)
tạo ra một đối tượngMutableState<Boolean>
giữ giá trị của state (trong trường hợp này làtrue
).remember {}
giúp Compose nhớ giá trị được truyền vào, để không thực thi lại lambda trong mỗi lần tái cấu trúc (recomposition).by
là từ khóa của Kotlin giúp ủy quyền (delegation). Nó ẩn đi thực tế rằngmutableStateOf()
trả về một đối tượngMutableState
, giúp chúng ta sử dụngenabled
như thể nó là một boolean thông thường. Ở đây ta cũng có thể không cần sử dụng by mà thay vào đó ta sử dụng dấu ‘=‘ trực tiếp. Nhưng để truy cập vào giá trị của biếnenabled
thì chúng ta phải dùngenabled.value
.
Stateful và Stateless Composables
- Stateful Composables: Chứa và quản lý state bên trong nó.
- Stateless Composables: Nhận state từ bên ngoài, không tự quản lý state.
Khi nào nên sử dụng Stateless Composables?
Hầu hết trường hợp, bạn nên sử dụng stateless composables. Điều này giúp việc phát triển và kiểm thử dễ dàng hơn, vì toàn bộ state được quản lý ở một nơi (thường là ViewModel
).
@Composable fun MyButton(label: String, onClick: () -> Unit) { Button(onClick) { Text(label) } }
Khi nào nên sử dụng Stateful Composables?
Đối với các Composables ở cấp màn hình (Screen-level) thì nó sẽ phù hợp để giữ state.
@Composable fun HomeScreen() { val homeViewModel = viewModel { HomeScreenViewModel() } val state by homeViewModel.inputText }
Cách giữ State trong ViewModel
Ví dụ về cách lưu trữ State trong ViewModel
class HomeScreenViewModel : ViewModel() { var inputText by mutableStateOf("") private set fun onTextChanged(text: String) { viewModelScope.launch { inputText = text } } }
@Composable fun HomeScreen() { val viewModel = viewModel { HomeScreenViewModel() } Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { MyTextField(viewModel.inputText) { viewModel.onTextChanged(it) } }} @Composable fun MyTextField(value: String, onTextChanged: (String) -> Unit) { TextField( value = value, onValueChange = { onTextChanged(it) } ) }
Cách sử dụng Flow, RxJava, LiveData để biểu diễn State trong Jetpack Compose
Kotlin Flow
val flow = MutableStateFlow("") val state by flow.collectAsState() val state by flow.collectAsStateWithLifecycle()
LiveData
val liveData = MutableLiveData<String>() val state by liveData.observeAsState()
RxJava
val observable = Observable.just("A", "B", "C") val state by observable.subscribeAsState("initial")
derivedStateOf
derivedStateOf
được sử dụng để lấy (lắng nghe) giá trị của các state khác. Nó chỉ thực hiện quá trình tính toán lại khi các state nó lắng nghe bị thay đổi, điều này giúp tối ưu hiệu năng.
@Composable fun DerivedStateOfExample() { val items = remember { mutableStateListOf(1, 2, 3) } val sum by remember { derivedStateOf { items.sum() } } Column { Button(onClick = { items.add((1..10).random()) }) { Text(text = "Add Item") } Text(text = "Sum: $sum") } }
Ví dụ khác:
@Composable fun DerivedStateExample() { var textInput by remember { mutableStateOf("") } var isEnabled by remember { mutableStateOf(true) } // Sử dụng derivedStateOf để tính toán giá trị mới dựa trên textInput và isEnabled val isSubmitEnabled by derivedStateOf { textInput.isNotEmpty() && isEnabled } Column( modifier = Modifier.fillMaxSize().padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { TextField( value = textInput, onValueChange = { textInput = it }, label = { Text("Enter text") } ) Spacer(modifier = Modifier.height(16.dp)) Button( onClick = { /* Handle submit */ }, enabled = isSubmitEnabled // Sử dụng derived state cho việc điều kiện bật nút ) { Text("Submit") } } }
State trong Jetpack Compose là một khái niệm cốt lõi giúp giao diện người dùng (UI) phản ứng tự động với dữ liệu thay đổi. Bằng cách sử dụng local state, state hoisting, và các kỹ thuật quản lý state như remember
, rememberSaveable
, derivedStateOf
, cùng với các hiệu ứng vòng đời như LaunchedEffect
và DisposableEffect
, bạn có thể tạo ra UI linh hoạt, hiệu quả và dễ bảo trì.
Việc hiểu và áp dụng đúng cách state management không chỉ giúp cải thiện hiệu suất ứng dụng mà còn làm cho mã nguồn dễ đọc, dễ mở rộng và dễ kiểm thử hơn. Hãy luôn ghi nhớ nguyên tắc stateless composables để tăng khả năng tái sử dụng và đảm bảo sự phân tách trách nhiệm hợp lý giữa UI và logic nghiệp vụ.
Với Jetpack Compose, quản lý state trở nên đơn giản hơn so với cách tiếp cận truyền thống, mang lại trải nghiệm phát triển hiện đại và tối ưu hơn. Hy vọng rằng bài viết này đã giúp bạn có cái nhìn rõ ràng hơn về State trong Jetpack Compose, từ đó áp dụng hiệu quả vào các dự án Android của mình!
Leave a Reply