State trong Jetpack Compose

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 composablesstateful composables, cũng như các phương pháp quản lý state như State HoistingViewModel, và các công nghệ liên quan như LiveDataFlow, 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ượng MutableState<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ằng mutableStateOf() trả về một đối tượng MutableState, giúp chúng ta sử dụng enabled 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ến enabled thì chúng ta phải dùng enabled.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ư LaunchedEffectDisposableEffect, 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

Your email address will not be published. Required fields are marked *