Compose và các thư viện khác

Bạn có thể sử dụng các thư viện yêu thích trong Compose. Mục này mô tả cách kết hợp một số thư viện hữu ích nhất.

Hoạt động

Để sử dụng Compose trong một hoạt động, bạn phải sử dụng ComponentActivity, một lớp con của Activity cung cấp LifecycleOwner thích hợp và các thành phần cho Compose. Lớp con này cũng cung cấp các API bổ sung giúp tách mã khỏi các phương thức ghi đè trong lớp hoạt động của bạn. Activity Compose hiển thị những API này với các thành phần kết hợp sao cho không cần phải ghi đè những phương thức diễn ra bên ngoài các thành phần kết hợp hoặc truy xuất một thực thể Activity rõ ràng. Thêm vào đó, các API này đảm bảo chúng chỉ được khởi chạy một lần, duy trì quá trình tái cấu trúc, đồng thời dọn dẹp đúng cách nếu thành phần kết hợp bị xoá khỏi cấu trúc.

Kết quả hoạt động

API rememberLauncherForActivityResult() cho phép bạn nhận kết quả từ một hoạt động trong thành phần kết hợp của bạn:

@Composable
fun GetContentExample() {
    var imageUri by remember { mutableStateOf<Uri?>(null) }
    val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
        imageUri = uri
    }
    Column {
        Button(onClick = { launcher.launch("image/*") }) {
            Text(text = "Load Image")
        }
        Image(
            painter = rememberAsyncImagePainter(imageUri),
            contentDescription = "My Image"
        )
    }
}

Ví dụ này minh hoạ một hợp đồng GetContent() đơn giản. Nhấn vào nút để khởi chạy yêu cầu. Cú pháp trailing Lambda cho rememberLauncherForActivityResult() được dẫn ra khi người dùng chọn một hình ảnh và quay lại hoạt động khởi chạy. Thao tác này tải hình ảnh đã chọn bằng hàm rememberImagePainter() của Coil.

Bạn có thể sử dụng bất kỳ lớp con nào củaActivityResultContract làm đối số đầu tiên cho rememberLauncherForActivityResult(). Tức là bạn có thể sử dụng kỹ thuật này để yêu cầu nội dung từ khung và các mẫu phổ biến khác. Bạn cũng có thể tạo các hợp đồng tuỳ chỉnh của riêng mình và sử dụng chúng bằng kỹ thuật này.

Yêu cầu quyền khi bắt đầu chạy

Bạn có thể dùng cùng một Activity Result API (API Kết quả hoạt động) và rememberLauncherForActivityResult() đã trình bày ở trên để yêu cầu cấp quyền khi bắt đầu chạy bằng cách sử dụng hợp đồng RequestPermission để được cấp một quyền hoặc sử dụng hợp đồng RequestMultiplePermissions để được cấp nhiều quyền.

Thư viện Quyền truy cập nhóm thư viện cũng có thể được sử dụng một lớp phía trên các API đó để liên kết trạng thái hiện tại đã cấp cho các quyền truy cập vào Trạng thái mà công cụ Compose có thể sử dụng.

Xử lý nút quay lại hệ thống

Để cung cấp hoạt động điều hướng quay lại tuỳ chỉnh và ghi đè chế độ mặc định của nút quay lại hệ thống từ bên trong thành phần kết hợp của bạn, thành phần kết hợp này có thể sử dụng hàm BackHandler để can thiệp vào sự kiện đó:

var backHandlingEnabled by remember { mutableStateOf(true) }
BackHandler(backHandlingEnabled) {
    // Handle back press
}

Đối số đầu tiên kiểm soát mã BackHandler có đang được bật hay không; bạn có thể sử dụng đối số này để tạm thời tắt trình xử lý dựa trên trạng thái của thành phần. Biểu thức lambda tạo vệt sẽ được gọi nếu người dùng kích hoạt sự kiện quay lại hệ thống và BackHandler hiện đang bật.

ViewModel

Nếu sử dụng thư viện Thành phần cấu trúc ViewModel, bạn có thể truy cập vào ViewModel từ bất kỳ thành phần kết hợp nào bằng cách gọi hàm viewModel(). Thêm phần phụ thuộc sau vào tệp Gradle:

Groovy

dependencies {
    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5'
}

Kotlin

dependencies {
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5")
}

Sau đó, bạn có thể sử dụng hàm viewModel() trong mã của mình.

class MyViewModel : ViewModel() { /*...*/ }

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    // use viewModel here
}

viewModel() trả về một chế độ ViewModel hiện có hoặc tạo một chế độ mới. Theo mặc định, ViewModel được trả về nằm trong phạm vi của hoạt động bao quanh, mảnh hoặc đích điều hướng, đồng thời được giữ lại chừng nào phạm vi còn hoạt động.

Ví dụ: nếu sử dụng thành phần kết hợp trong một hoạt động, viewModel() sẽ trả về cùng một phiên bản cho đến khi hoạt động đó kết thúc hoặc quá trình kết thúc.

class MyViewModel : ViewModel() { /*...*/ }
// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    // Returns the same instance as long as the activity is alive,
    // just as if you grabbed the instance from an Activity or Fragment
    viewModel: MyViewModel = viewModel()
) { /* ... */ }

@Composable
fun MyScreen2(
    viewModel: MyViewModel = viewModel() // Same instance as in MyScreen
) { /* ... */ }

Nguyên tắc sử dụng

Bạn thường truy cập vào các thực thể ViewModel ở thành phần kết hợp cấp màn hình, tức là gần với một thành phần kết hợp gốc được gọi từ một hoạt động, mảnh hoặc đích đến của biểu đồ Điều hướng. Điều này là do theo mặc định, ViewModel được đưa vào phạm vi của các đối tượng cấp màn hình đó. Đọc thêm về vòng đời và phạm vi của ViewModel tại đây.

Cố gắng tránh truyền các thực thể ViewModel xuống các thành phần kết hợp khác vì điều này có thể khiến các thành phần kết hợp đó khó kiểm thử hơn và có thể làm hỏng bản xem trước. Thay vào đó, hãy chỉ truyền dữ liệu và các hàm cần thiết dưới dạng tham số.

Bạn có thể sử dụng các thực thể ViewModel để quản lý trạng thái cho các thành phần kết hợp cấp màn hình phụ, tuy nhiên, hãy lưu ý đến vòng đời và phạm vi của ViewModel. Nếu thành phần kết hợp là độc lập, bạn nên cân nhắc sử dụng Hilt để chèn ViewModel nhằm tránh phải truyền các phần phụ thuộc từ thành phần kết hợp mẹ.

Nếu ViewModel có các phần phụ thuộc, viewModel() sẽ lấy ViewModelProvider.Factory tuỳ ý làm tham số.

Để biết thêm thông tin về ViewModel trong Compose và cách sử dụng các phiên bản với thư viện Compose Navigation, hoặc các hoạt động và mảnh, vui lòng xem tài liệu về Khả năng tương tác.

Trình phát dữ liệu trực tuyến

Compose đi kèm với phần mở rộng cho các giải pháp dựa trên chế độ phát trực tuyến phổ biến nhất của Android. Mỗi phần mở rộng trong số này được một cấu phần phần mềm khác nhau cung cấp:

  • LiveData.observeAsState() có trong cấu phần phần mềm androidx.compose.runtime:runtime-livedata:$composeVersion.
  • Flow.collectAsState() không yêu cầu thêm phần phụ thuộc.
  • Observable.subscribeAsState() có trong cấu phần phần mềm androidx.compose.runtime:runtime-rxjava2:$composeVersion hoặc androidx.compose.runtime:runtime-rxjava3:$composeVersion.

Các cấu phần phần mềm này đăng ký dưới dạng trình nghe và thể hiện các giá trị dưới dạng một State. Bất cứ khi nào có giá trị mới được đưa ra, công cụ Compose sẽ kết hợp lại các phần đó của giao diện người dùng nơi sử dụng state.value. Ví dụ: trong mã này, ShowData sẽ kết hợp lại mỗi khi exampleLiveData đưa ra một giá trị mới.

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    val dataExample = viewModel.exampleLiveData.observeAsState()

    // Because the state is read here,
    // MyScreen recomposes whenever dataExample changes.
    dataExample.value?.let {
        ShowData(dataExample)
    }
}

Thao tác không đồng bộ trong công cụ Compose

Jetpack Compose cho phép bạn thực thi các thao tác không đồng bộ bằng cách sử dụng coroutine từ trong những thành phần kết hợp của bạn.

Hãy xem các API LaunchedEffect, produceStaterememberCoroutineScope trong tài liệu về hiệu ứng lề để biết thêm thông tin.

Thành phần điều hướng hỗ trợ các ứng dụng sử dụng công cụ Jetpack Compose. Hãy xem phần Điều hướng bằng ComposeDi chuyển điều hướng Jetpack sang Navigation Compose để biết thêm thông tin.

Hilt

Hilt là giải pháp được đề xuất để chèn phần phụ thuộc vào các ứng dụng Android, giải pháp này hoạt động liền mạch với Compose.

Hàm viewModel() được đề cập trong Mục ViewModel sẽ tự động sử dụng ViewModel mà Hilt tạo ra với chú thích @HiltViewModel. Chúng tôi đã cung cấp cho bạn tài liệu với thông tin về Tích hợp ViewModel của Hilt.

@HiltViewModel
class MyViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle,
    private val repository: ExampleRepository
) : ViewModel() { /* ... */ }

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) { /* ... */ }

Hilt và điều hướng

Hilt cũng tích hợp với thư viện Navigation Compose. Thêm các phần phụ thuộc bổ sung sau đây vào tệp Gradle của bạn:

Groovy

dependencies {
    implementation 'androidx.hilt:hilt-navigation-compose:1.2.0'
}

Kotlin

dependencies {
    implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
}

Khi sử dụng tính năng Navigation Compose, hãy luôn sử dụng hàm kết hợp hiltViewModel để có được bản sao của @HiltViewModel có chú thích ViewModel. Tính năng này hoạt động với các mảnh hoặc hoạt động được chú giải bằng @AndroidEntryPoint.

Ví dụ: nếu ExampleScreen là một đích đến trong một biểu đồ điều hướng, hãy gọi hiltViewModel() để lấy thực thể của ExampleViewModel trong phạm vi đích đến được hiển thị trong đoạn mã dưới đây:

// import androidx.hilt.navigation.compose.hiltViewModel

@Composable
fun MyApp() {
    val navController = rememberNavController()
    val startRoute = "example"
    NavHost(navController, startDestination = startRoute) {
        composable("example") { backStackEntry ->
            // Creates a ViewModel from the current BackStackEntry
            // Available in the androidx.hilt:hilt-navigation-compose artifact
            val viewModel = hiltViewModel<MyViewModel>()
            MyScreen(viewModel)
        }
        /* ... */
    }
}

Nếu bạn cần truy xuất bản sao của ViewModel nằm trong phạm vi tuyến đường điều hướng hoặc sơ đồ điều hướng thay thế, sử dụng hàm có khả năng kết hợp hiltViewModel và truyền tham số backStackEntry tương ứng:

// import androidx.hilt.navigation.compose.hiltViewModel
// import androidx.navigation.compose.getBackStackEntry

@Composable
fun MyApp() {
    val navController = rememberNavController()
    val startRoute = "example"
    val innerStartRoute = "exampleWithRoute"
    NavHost(navController, startDestination = startRoute) {
        navigation(startDestination = innerStartRoute, route = "Parent") {
            // ...
            composable("exampleWithRoute") { backStackEntry ->
                val parentEntry = remember(backStackEntry) {
                    navController.getBackStackEntry("Parent")
                }
                val parentViewModel = hiltViewModel<ParentViewModel>(parentEntry)
                ExampleWithRouteScreen(parentViewModel)
            }
        }
    }
}

Paging

Thư viện Paging giúp bạn tải dữ liệu dễ dàng hơn và tính năng này được hỗ trợ trong công cụ Compose. Trang phát hành Paging có chứa thông tin về phần phụ thuộc paging-compose bổ sung cần được thêm vào dự án và phiên bản của dự án đó.

Dưới đây là một ví dụ về các API Compose của thư viện Paging:

@Composable
fun MyScreen(flow: Flow<PagingData<String>>) {
    val lazyPagingItems = flow.collectAsLazyPagingItems()
    LazyColumn {
        items(
            lazyPagingItems.itemCount,
            key = lazyPagingItems.itemKey { it }
        ) { index ->
            val item = lazyPagingItems[index]
            Text("Item is $item")
        }
    }
}

Hãy xem Tài liệu về Danh sách và lưới để biết thêm thông tin về cách sử dụng tính năng Phân trang trong Compose.

Maps

Bạn có thể sử dụng thư viện Maps Compose để tích hợp Google Maps vào ứng dụng của mình. Sau đây là ví dụ về cách sử dụng:

@Composable
fun MapsExample() {
    val singapore = LatLng(1.35, 103.87)
    val cameraPositionState = rememberCameraPositionState {
        position = CameraPosition.fromLatLngZoom(singapore, 10f)
    }
    GoogleMap(
        modifier = Modifier.fillMaxSize(),
        cameraPositionState = cameraPositionState
    ) {
        Marker(
            state = remember { MarkerState(position = singapore) },
            title = "Singapore",
            snippet = "Marker in Singapore"
        )
    }
}