Compose 및 기타 라이브러리

Compose에서는 자주 이용하는 라이브러리를 사용할 수 있습니다. 이 섹션에서는 몇 가지 가장 유용한 라이브러리를 통합하는 방법을 설명합니다.

활동

활동에서 Compose를 사용하려면 적절한 LifecycleOwner와 구성요소를 Compose에 제공하는 Activity의 서브클래스인 ComponentActivity를 사용해야 합니다. 이 서브클래스는 활동 클래스의 메서드 재정의에서 코드를 분리하는 추가 API를 제공합니다. Activity Compose는 이러한 API를 컴포저블에 노출합니다. 따라서 컴포저블 외부에서 메서드를 재정의하거나 명시적 Activity 인스턴스를 더 이상 가져오지 않아도 됩니다. 또한 이러한 API는 한 번만 초기화되고, 재구성에도 그대로 유지되며, 컴포저블이 구성에서 삭제되는 경우 적절하게 정리됩니다.

활동 결과

rememberLauncherForActivityResult() API를 사용하면 컴포저블의 활동에서 결과를 가져올 수 있습니다.

@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"
        )
    }
}

이 예시는 간단한 GetContent() 계약을 보여줍니다. 버튼을 탭하면 요청이 실행됩니다. rememberLauncherForActivityResult()의 후행 람다는 사용자가 이미지를 선택하고 실행 활동으로 돌아가면 호출됩니다. 그러면 Coil의 rememberImagePainter() 함수를 통해 선택한 이미지가 로드됩니다.

ActivityResultContract의 서브클래스는 rememberLauncherForActivityResult()의 첫 번째 인수로 사용할 수 있습니다. 즉, 이 기법을 사용하여 프레임워크와 다른 일반 패턴에서 콘텐츠를 요청할 수 있습니다. 또한 맞춤 계약을 자체적으로 만들어 이 기법과 함께 사용할 수도 있습니다.

런타임 권한 요청

단일 권한에 RequestPermission 계약을 사용하거나 여러 권한에 RequestMultiplePermissions 계약을 사용하여 런타임 권한을 요청할 때, 위에서 설명한 동일한 Activity Result API와 rememberLauncherForActivityResult()를 사용할 수 있습니다.

권한에 현재 부여된 상태를 Compose UI에 사용할 수 있는 상태로 매핑할 때 Accompanist Permissions 라이브러리를 이러한 API 위에 하나의 계층으로 사용할 수도 있습니다.

시스템 뒤로 버튼 처리

맞춤 뒤로 탐색을 제공하고 컴포저블 내에서 시스템 뒤로 버튼의 기본 동작을 재정의하려면 컴포저블이 BackHandler를 사용하여 관련 이벤트를 가로채면 됩니다.

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

첫 번째 인수는 BackHandler의 현재 사용 설정 여부를 제어합니다. 이 인수를 사용하여 구성요소 상태에 따라 핸들러를 일시적으로 중지할 수 있습니다. 사용자가 시스템 뒤로 이벤트를 트리거하고 BackHandler가 현재 사용 설정된 경우 후행 람다가 호출됩니다.

ViewModel

아키텍처 구성요소 ViewModel 라이브러리를 사용하는 경우 viewModel() 함수를 호출하여 컴포저블에서 ViewModel에 액세스할 수 있습니다. Gradle 파일에 다음 종속 항목을 추가합니다.

Groovy

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

Kotlin

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

그런 다음 코드에서 viewModel() 함수를 사용할 수 있습니다.

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

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

viewModel()은 기존 ViewModel을 반환하거나 새 ViewModel을 만듭니다. 기본적으로 반환된 ViewModel는 래핑된 활동, 프래그먼트 또는 탐색 대상의 범위로 지정되며 범위가 유지되는 동안 유지됩니다.

예를 들어 컴포저블이 활동에서 사용되는 경우 viewModel()은 활동이 완료되거나 프로세스가 종료될 때까지 동일한 인스턴스를 반환합니다.

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
) { /* ... */ }

사용 가이드라인

일반적으로 ViewModel 인스턴스는 탐색 그래프의 활동, 프래그먼트, 대상에서 호출된 루트 컴포저블에 가까운 화면 수준 컴포저블에서 액세스합니다. 이는 ViewModel가 기본적으로 이러한 화면 수준 객체로 범위가 지정되기 때문입니다. ViewModel수명 주기 및 범위에 대해 자세히 알아보세요.

ViewModel 인스턴스를 다른 컴포저블에 전달하지 마세요. 이렇게 하면 해당 컴포저블을 테스트하기가 더 어려워지고 미리보기가 중단될 수 있습니다. 대신 필요한 데이터와 함수만 매개변수로 전달하세요.

ViewModel 인스턴스를 사용하여 하위 화면 수준 컴포저블의 상태를 관리할 있지만 ViewModel수명 주기 및 범위에 유의하세요. 컴포저블이 독립형인 경우 상위 컴포저블에서 종속 항목을 전달하지 않아도 되도록 Hilt를 사용하여 ViewModel를 삽입하는 것이 좋습니다.

ViewModel에 종속 항목이 있는 경우 viewModel()은 선택적 ViewModelProvider.Factory를 매개변수로 사용합니다.

Compose의 ViewModel, 인스턴스가 Navigation Compose 라이브러리와 함께 사용되는 방식 또는 활동 및 프래그먼트에 관한 자세한 내용은 상호 운용성 문서를 참고하세요.

데이터 스트림

Compose에는 Android에서 가장 많이 사용되는 스트림 기반 솔루션을 위한 확장이 함께 제공됩니다. 이러한 각 확장은 다음과 같은 다양한 아티팩트에 의해 제공됩니다.

  • LiveData.observeAsState()androidx.compose.runtime:runtime-livedata:$composeVersion 아티팩트에 포함됩니다.
  • Flow.collectAsState()는 추가 종속 항목이 필요하지 않습니다.
  • Observable.subscribeAsState()androidx.compose.runtime:runtime-rxjava2:$composeVersion 또는 androidx.compose.runtime:runtime-rxjava3:$composeVersion 아티팩트에 포함됩니다.

이러한 아티팩트는 리스너로 등록되며 값을 State로 나타냅니다. 새 값이 생성될 때마다 Compose는 state.value가 사용되는 UI의 해당 부분을 재구성합니다. 예를 들어 다음 코드에서 ShowDataexampleLiveData가 새 값을 내보낼 때마다 재구성됩니다.

// 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)
    }
}

Compose의 비동기 작업

Jetpack Compose를 사용하면 컴포저블 내에서 코루틴을 사용하여 비동기 작업을 실행할 수 있습니다.

자세한 내용은 부수 효과 문서에서 LaunchedEffect, produceStaterememberCoroutineScope API를 참고하세요.

Navigation 구성요소는 Jetpack Compose 애플리케이션을 지원합니다. 자세한 내용은 Compose를 통해 이동Jetpack Navigation을 Navigation Compose로 이전을 참고하세요.

Hilt

Hilt는 Android 앱에서 종속 항목 삽입을 위해 권장되는 솔루션이며 Compose와 함께 원활하게 작동합니다.

ViewModel 섹션에서 언급된 viewModel() 함수는 Hilt가 @HiltViewModel 주석을 사용하여 구성하는 ViewModel을 자동으로 사용합니다. 자세한 내용은 Hilt의 ViewModel 통합에 관한 문서를 참고하세요.

@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 및 탐색

Hilt는 Navigation Compose 라이브러리와도 통합됩니다. Gradle 파일에 다음과 같은 종속 항목을 추가합니다.

Groovy

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

Kotlin

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

Navigation Compose를 사용할 때는 항상 구성 가능한 hiltViewModel 함수를 사용하여 @HiltViewModel 주석이 달린 ViewModel의 인스턴스를 가져옵니다. 이는 @AndroidEntryPoint 주석이 달린 프래그먼트 또는 활동에서 작동합니다.

예를 들어 ExampleScreen이 탐색 그래프의 대상이면 hiltViewModel()을 호출하여 아래 코드 스니펫과 같이 해당 대상으로 범위가 지정된 ExampleViewModel의 인스턴스를 가져옵니다.

// 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)
        }
        /* ... */
    }
}

대신 탐색 경로탐색 그래프로 범위가 지정된 ViewModel 인스턴스를 가져와야 한다면 구성 가능한 hiltViewModel 함수를 사용하고 상응하는 backStackEntry를 매개변수로 전달합니다.

// 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

Paging 라이브러리를 사용하면 데이터를 점진적이고 매끄럽게 로드할 수 있으며 Paging 라이브러리는 Compose에서 지원됩니다. Paging 출시 페이지에는 프로젝트에 추가해야 하는 추가 paging-compose 종속 항목 및 버전에 관한 정보가 포함되어 있습니다.

다음은 Paging 라이브러리의 Compose API의 예입니다.

@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")
        }
    }
}

Compose에서 Paging을 사용하는 것에 관한 자세한 내용은 목록 및 그리드 문서를 참고하세요.

지도

Maps Compose 라이브러리를 사용하여 앱에서 Google 지도를 제공할 수 있습니다. 사용 예는 다음과 같습니다.

@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"
        )
    }
}