Compose and other libraries

You can use your favorite libraries in Compose. This section describes how to incorporate a few of the most useful libraries.

Activity

To use Compose in an activity, you must use ComponentActivity, a subclass of Activity that provides the appropriate LifecycleOwner and components to Compose. It also provides additional APIs that decouple your code from overriding methods in your activity class. Activity Compose exposes these APIs to composables such that overriding methods outside of your composables or retrieving an explicit Activity instance is no longer required. Moreover, these APIs ensure that they are only initialized once, survive recomposition, and clean up properly if the composable is removed from the composition.

Activity Result

The rememberLauncherForActivityResult() API allows you to get a result from an activity in your composable:

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

This example demonstrates a simple GetContent() contract. Tapping the button launches the request. The trailing lambda for rememberLauncherForActivityResult() is invoked once the user selects an image and returns to the launching activity. This loads the selected image using Coil’s rememberImagePainter() function.

Any subclass of ActivityResultContract can be used as the first argument to rememberLauncherForActivityResult(). This means that you can use this technique to request content from the framework and in other common patterns. You can also create your own custom contracts and use them with this technique.

Requesting runtime permissions

The same Activity Result API and rememberLauncherForActivityResult() explained above can be used to request runtime permissions using the RequestPermission contract for a single permission or RequestMultiplePermissions contract for multiple permissions.

The Accompanist Permissions library can also be used a layer above those APIs to map the current granted state for permissions into State that your Compose UI can use.

Handling the system back button

To provide custom back navigation and override the default behavior of the system back button from within your composable, your composable can use a BackHandler to intercept that event:

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

The first argument controls whether the BackHandler is currently enabled; you can use this argument to temporarily disable your handler based on the state of your component. The trailing lambda will be invoked if the user triggers a system back event, and the BackHandler is currently enabled.

ViewModel

If you use the Architecture Components ViewModel library, you can access a ViewModel from any composable by calling the viewModel() function. Add the following dependency to your Gradle file:

Groovy

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

Kotlin

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

You can then use the viewModel() function in your code.

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

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

viewModel() returns an existing ViewModel or creates a new one. By default, the returned ViewModel is scoped to the enclosing activity, fragment or navigation destination, and is retained as long as the scope is alive.

For example, if the composable is used in an activity, viewModel() returns the same instance until the activity is finished or the process is killed.

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

Usage guidelines

You usually access ViewModel instances at screen-level composables, that is, close to a root composable called from an activity, fragment, or destination of a Navigation graph. This is because ViewModels are, by default, scoped to those screen level objects. Read more about a ViewModel's lifecycle and scope here.

Try to avoid passing down ViewModel instances to other composables as this can make those composables more difficult to test and can break previews. Instead, pass only the data and functions they need as parameters.

You can use ViewModel instances to manage state for sub screen-level composables, however, be aware of the ViewModel's lifecycle and scope. If the composable is self-contained, you may want to consider using Hilt to inject the ViewModel to avoid having to pass dependencies from parent composables.

If your ViewModel has dependencies, viewModel() takes an optional ViewModelProvider.Factory as a parameter.

For more information about ViewModel in Compose and how instances are used with the Navigation Compose library, or activities and fragments, see the Interoperability docs.

Streams of data

Compose comes with extensions for Android's most popular stream-based solutions. Each of these extensions is provided by a different artifact:

These artifacts register as a listener and represent the values as a State. Whenever a new value is emitted, Compose recomposes those parts of the UI where that state.value is used. For example, in this code, ShowData recomposes every time exampleLiveData emits a new value.

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

Asynchronous operations in Compose

Jetpack Compose lets you execute asynchronous operations using coroutines from within your composables.

See the LaunchedEffect, produceState, and rememberCoroutineScope APIs in the side effects documentation for more information.

The Navigation component provides support for Jetpack Compose applications. See Navigating with Compose and Migrate Jetpack Navigation to Navigation Compose for more information.

Hilt

Hilt is the recommended solution for dependency injection in Android apps, and works seamlessly with Compose.

The viewModel() function mentioned in the ViewModel section automatically uses the ViewModel that Hilt constructs with the @HiltViewModel annotation. We've provided documentation with information about Hilt's ViewModel integration.

@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 and Navigation

Hilt also integrates with the Navigation Compose library. Add the following additional dependencies to your Gradle file:

Groovy

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

Kotlin

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

When using Navigation Compose, always use the hiltViewModel composable function to obtain an instance of your @HiltViewModel annotated ViewModel. This works with fragments or activities that are annotated with @AndroidEntryPoint.

For example, if ExampleScreen is a destination in a navigation graph, call hiltViewModel() to get an instance of ExampleViewModel scoped to the destination as shown in the code snippet below:

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

If you need to retrieve the instance of a ViewModel scoped to navigation routes or the navigation graph instead, use the hiltViewModel composable function and pass the corresponding backStackEntry as a parameter:

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

The Paging library makes it easier for you to load data gradually and it's supported in Compose. The Paging release page contains information about the extra paging-compose dependency that needs to be added to the project and its version.

Here's an example of the Paging library's Compose APIs:

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

Check out the Lists and grids documentation for more information about using Paging in Compose.

Maps

You can use the Maps Compose library to provide Google Maps in your app. Here's a usage example:

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