Compose et autres bibliothèques

Vous pouvez utiliser vos bibliothèques préférées dans Compose. Cette section vous présente comment intégrer quelques-unes des bibliothèques les plus utiles.

Activité

Pour utiliser Compose dans une activité, vous devez utiliser ComponentActivity, une sous-classe de Activity qui fournit les LifecycleOwner et les composants appropriés à Compose. Elle fournit également des API supplémentaires qui dissocient votre code pour ne pas remplacer les méthodes dans votre classe d'activité. Activity Compose expose ces API à des composables. Il n'est donc plus nécessaire de remplacer les méthodes en dehors de vos composables ou de récupérer une instance Activity explicite. De plus, ces API garantissent que les composables qu'elles ne sont initialisées qu'une seule fois, qu'elles survivent à la recomposition et qu'elles sont supprimées proprement si le composable est supprimé de la composition.

Résultat d'activité

L'API rememberLauncherForActivityResult() vous permet d'obtenir un résultat depuis une activité dans votre 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"
        )
    }
}

Cet exemple illustre un contrat GetContent() simple. Appuyez sur le bouton pour lancer la demande. Le lambda de fin pour rememberLauncherForActivityResult() est appelé une fois que l'utilisateur sélectionne une image et revient au lancement de l'activité. L'image sélectionnée est chargée à l'aide de la fonction rememberImagePainter() de Coil.

N'importe quelle sous-classe de ActivityResultContract peut être utilisée comme premier argument de rememberLauncherForActivityResult(). Cela signifie que vous pouvez utiliser cette technique pour demander du contenu au framework et d'autres modèles courants. Vous pouvez également créer vos propres contrats personnalisés et les utiliser avec cette technique.

Demander des autorisations d'exécution

Il est possible d'utiliser l'API Activity Result et rememberLauncherForActivityResult(), mentionnés ci-dessus, pour demander des autorisations d'exécution à l'aide du contrat RequestPermission pour une seule autorisation ou du contrat RequestMultiplePermissions pour plusieurs autorisations.

La bibliothèque d'autorisations Accompanist peut également être utilisée au-dessus de ces API pour mapper l'état actuel des autorisations accordées à l'état que votre UI Compose peut utiliser.

Gérer le bouton "Retour" du système

Pour fournir une navigation Retour personnalisée et remplacer le comportement par défaut du bouton Retour du système depuis votre composable, votre composable peut utiliser un BackHandler pour intercepter cet événement :

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

Le premier argument contrôle si BackHandler est actuellement activé. Cet argument vous permet de désactiver temporairement votre gestionnaire en fonction de l'état de votre composant. Le lambda de fin sera appelé si l'utilisateur déclenche un événement système et si BackHandler est actuellement activé.

ViewModel

Si vous utilisez la bibliothèque Composants de l'architecture ViewModel, vous pouvez accéder à un ViewModel à partir de n'importe quel composable en appelant la méthode viewModel(). Ajoutez la dépendance suivante à votre fichier Gradle:

Groovy

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

Kotlin

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

Vous pouvez ensuite utiliser la fonction viewModel() dans votre code.

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

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

viewModel() renvoie un ViewModel existant ou en crée un. Par défaut, le ViewModel renvoyé est limité à l'activité, au fragment ou à la destination de navigation englobant, et est conservé tant que le champ d'application est actif.

Par exemple, si le composable est utilisé dans une activité, viewModel() renvoie la même instance jusqu'à la fin de l'activité ou la fermeture du processus.

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

Consignes d'utilisation

Vous accédez généralement aux instances ViewModel au niveau des composables au niveau de l'écran, c'est-à-dire à proximité d'un composable racine appelé depuis une activité, un fragment ou la destination d'un graphique de navigation. En effet, les ViewModel sont par défaut limitées à ces objets au niveau de l'écran. Pour en savoir plus sur le cycle de vie et la portée d'un ViewModel, consultez cet article.

Évitez de transmettre des instances ViewModel à d'autres composables, car cela peut rendre ces composables plus difficiles à tester et endommager les aperçus. Au lieu de cela, transmettez les données et les fonctions dont ils ont besoin en tant que paramètres.

Vous pouvez utiliser des instances ViewModel pour gérer l'état des composables au niveau de l'écran secondaire. Toutefois, tenez compte du cycle de vie et de la portée de ViewModel. Si le composable est autonome, vous pouvez envisager d'utiliser Hilt pour injecter le ViewModel afin d'éviter d'avoir à transmettre des dépendances à partir des composables parent.

Si votre ViewModel comporte des dépendances, viewModel() accepte un paramètre ViewModelProvider.Factory facultatif.

Pour en savoir plus sur le ViewModel dans Compose et sur l'utilisation des instances avec la bibliothèque Navigation de Compose, ou sur les activités et les fragments, consultez la documentation sur l'interopérabilité.

Flux de données

Compose propose des extensions pour les plus grandes solutions Android basées sur les flux. Chacune de ces extensions est fournie par un artefact différent :

Ces artefacts s'enregistrent en tant qu'écouteur et représentent les valeurs sous la forme d'un State. Chaque fois qu'une nouvelle valeur est émise, Compose recompose les parties de l'UI où state.value est utilisée. Par exemple, dans ce code, ShowData se recompose chaque fois que exampleLiveData émet une nouvelle valeur.

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

Opérations asynchrones dans Compose

Jetpack Compose vous permet d'exécuter des opérations asynchrones à l'aide de coroutines depuis vos composables.

Consultez les API LaunchedEffect, produceState et rememberCoroutineScope dans la documentation sur les effets secondaires pour en savoir plus.

Le composant Navigation est compatible avec les applications Jetpack Compose. Pour en savoir plus, consultez Naviguer avec Compose et Migrer Jetpack Navigation vers Navigation Compose.

Hilt

Il s'agit de la solution recommandée pour l'injection de dépendances dans les applications Android. Elle fonctionne parfaitement avec Compose.

La fonction viewModel() mentionnée dans la section ViewModel utilise automatiquement le ViewModel que Looker construit avec l'annotation @HiltViewModel. Nous avons fourni de la documentation sur l'intégration de ViewModel pour 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 et Navigation

Hilt s'intègre également à la bibliothèque de navigation Compose. Ajoutez les dépendances supplémentaires suivantes à votre fichier Gradle :

Groovy

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

Kotlin

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

Lorsque vous utilisez Navigation Compose, utilisez toujours la fonction modulable hiltViewModel pour obtenir une instance de votre ViewModel annotée @HiltViewModel. Cela fonctionne avec les fragments ou les activités annotés avec @AndroidEntryPoint.

Par exemple, si ExampleScreen est une destination dans un graphique de navigation, appelez hiltViewModel() pour obtenir une instance de ExampleViewModel limitée à la destination, comme indiqué dans l'extrait de code ci-dessous :

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

Si vous devez récupérer l'instance d'un ViewModel limité aux itinéraires de navigation ou au graphique de navigation, utilisez hiltViewModel et transmettez la valeur backStackEntry correspondante en tant que paramètre :

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

La bibliothèque Paging facilite le chargement progressif des données. Elle est compatible avec Compose. La page des versions de Paging contient des informations sur la dépendance paging-compose supplémentaire à ajouter au projet et à sa version.

Voici un exemple des API Compose de la bibliothèque 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")
        }
    }
}

Pour en savoir plus sur l'utilisation de Paging dans Compose, consultez la documentation sur les listes et les grilles.

Maps

La bibliothèque Maps Compose vous permet de proposer Google Maps dans votre application. Voici un exemple d'utilisation :

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