Tích hợp Compose vào cấu trúc ứng dụng hiện có

Mô hình cấu trúc Luồng dữ liệu một chiều (UDF) hoạt động liền mạch với Compose. Nếu ứng dụng dùng các loại mô hình cấu trúc khác, chẳng hạn như Mô hình – Khung hiển thị – Thành phần trình bày (MVP), bạn nên di chuyển phần giao diện người dùng đó sang UDF trước hoặc trong khi sử dụng Compose.

ViewModels trong Compose

Nếu bạn sử dụng thư viện Thành phần kiến trúc ViewModel, bạn có thể truy cập ViewModel từ bất kỳ thành phần kết hợp nào bằng cách gọi hàm viewModel() như giải thích trong Tài liệu về tích hợp Compose với các thư viện chung.

Khi dùng Compose, hãy cẩn thận về việc sử dụng cùng một loại ViewModel trong các thành phần kết hợp khác nhau dưới dạng phần tử ViewModel tuân theo phạm vi vòng đời của Khung hiển thị. Phạm vi này sẽ là hoạt động lưu trữ, mảnh hoặc biểu đồ điều hướng nếu sử dụng thư viện Điều hướng.

Ví dụ: nếu các thành phần kết hợp được lưu trữ trong một hoạt động, thì viewModel() luôn trả về cùng một phiên bản, phiên bản này chỉ bị xoá khi hoạt động đó kết thúc. Ở ví dụ sau, cùng một người dùng ("user1") sẽ được chào hai lần vì cùng phiên bản của GreetingViewModel được sử dụng lại trong tất cả các thành phần kết hợp dưới hoạt động của máy chủ. Phiên bản ViewModel đầu tiên đã tạo được sử dụng lại trong các thành phần kết hợp khác.

class GreetingActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            MaterialTheme {
                Column {
                    GreetingScreen("user1")
                    GreetingScreen("user2")
                }
            }
        }
    }
}

@Composable
fun GreetingScreen(
    userId: String,
    viewModel: GreetingViewModel = viewModel(
        factory = GreetingViewModelFactory(userId)
    )
) {
    val messageUser by viewModel.message.observeAsState("")
    Text(messageUser)
}

class GreetingViewModel(private val userId: String) : ViewModel() {
    private val _message = MutableLiveData("Hi $userId")
    val message: LiveData<String> = _message
}

class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return GreetingViewModel(userId) as T
    }
}

Vì biểu đồ điều hướng cũng đặt phạm vi các phần tử ViewModel, các thành phần kết hợp là đích đến trong biểu đồ điều hướng sẽ có một phiên bản ViewModel khác. Trong trường hợp này, ViewModel nằm trong phạm vi vòng đời của đích đến và sẽ bị xoá khi đích đến bị xoá khỏi ngăn xếp lùi. Trong ví dụ sau đây, khi người dùng chuyển đến màn hình Hồ sơ, thao tác này sẽ tạo một phiên bản mới của GreetingViewModel.

@Composable
fun MyApp() {
    NavHost(rememberNavController(), startDestination = "profile/{userId}") {
        /* ... */
        composable("profile/{userId}") { backStackEntry ->
            GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "")
        }
    }
}

Nguồn trạng thái đáng tin

Khi bạn sử dụng Compose trong một phần của giao diện người dùng, có thể Compose và mã hệ thống Chế độ xem sẽ cần phải chia sẻ dữ liệu. Khi có thể, bạn nên đóng gói trạng thái được chia sẻ đó trong một lớp khác tuân theo các phương pháp hay nhất của UDF mà cả hai nền tảng sử dụng, chẳng hạn như trong ViewModel để hiển thị luồng dữ liệu được chia sẻ để phát các nội dung cập nhật dữ liệu.

Tuy nhiên, không phải lúc nào điều đó cũng có thể xảy ra nếu dữ liệu được chia sẻ có thể thay đổi hoặc liên kết chặt chẽ với một thành phần trên giao diện người dùng. Trong trường hợp đó, một hệ thống phải là nguồn đáng tin và hệ thống đó cần chia sẻ mọi nội dung cập nhật dữ liệu cho hệ thống kia. Theo quy tắc chung, nguồn đáng tin phải thuộc sở hữu của thành phần gần hơn với gốc của hệ phân cấp giao diện người dùng.

Compose là nguồn đáng tin

Sử dụng thành phần kết hợp SideEffect để xuất bản trạng thái Compose thành các mã không phải Compose. Trong trường hợp này, nguồn đáng tin sẽ được giữ lại trong một thành phần kết hợp có nhiệm vụ gửi các lượt cập nhật trạng thái.

Ví dụ: thư viện phân tích của bạn có thể cho phép bạn phân đoạn toàn bộ số người dùng bằng cách đính kèm siêu dữ liệu tuỳ chỉnh (thuộc tính người dùng trong ví dụ này) vào tất cả các sự kiện phân tích tiếp theo. Để truyền đạt thông tin loại người dùng của người dùng hiện tại cho thư viện phân tích của bạn, hãy sử dụng SideEffect để cập nhật giá trị.

@Composable
fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics {
    val analytics: FirebaseAnalytics = remember {
        /* ... */
    }

    // On every successful composition, update FirebaseAnalytics with
    // the userType from the current User, ensuring that future analytics
    // events have this metadata attached
    SideEffect {
        analytics.setUserProperty("userType", user.userType)
    }
    return analytics
}

Để biết thêm thông tin, hãy xem tài liệu về các hiệu ứng lề.

Hệ thống chế độ xem là nguồn đáng tin

Nếu hệ thống chế độ xem sở hữu trạng thái và chia sẻ trạng thái đó với Compose, bạn nên bao gồm trạng thái đó trong các đối tượng mutableStateOf để trạng thái an toàn theo luồng cho Compose. Nếu bạn sử dụng cách tiếp cận này, các hàm có khả năng kết hợp được đơn giản hoá vì các hàm đó không còn nguồn đáng tin nữa. Tuy nhiên, hệ thống Khung hiển thị cần cập nhật trạng thái có thể thay đổi và các Khung hiển thị sử dụng trạng thái đó.

Trong ví dụ sau, một CustomViewGroup chứa TextViewComposeView có thành phần kết hợp TextField bên trong. TextView cần hiển thị nội dung mà người dùng nhập vào trong TextField.

class CustomViewGroup @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : LinearLayout(context, attrs, defStyle) {

    // Source of truth in the View system as mutableStateOf
    // to make it thread-safe for Compose
    private var text by mutableStateOf("")

    private val textView: TextView

    init {
        orientation = VERTICAL

        textView = TextView(context)
        val composeView = ComposeView(context).apply {
            setContent {
                MaterialTheme {
                    TextField(value = text, onValueChange = { updateState(it) })
                }
            }
        }

        addView(textView)
        addView(composeView)
    }

    // Update both the source of truth and the TextView
    private fun updateState(newValue: String) {
        text = newValue
        textView.text = newValue
    }
}