Utiliser les vues dans Compose

Vous pouvez inclure une hiérarchie des vues Android dans une interface utilisateur Compose. Cette approche est particulièrement utile si vous souhaitez utiliser des éléments d'interface utilisateur qui ne sont pas encore disponibles dans Compose, comme AdView. Cela vous permet également de réutiliser des vues personnalisées.

Pour inclure un élément ou une hiérarchie de vues, utilisez le composable AndroidView . AndroidView reçoit un lambda qui renvoie une View. AndroidView fournit également un rappel update, qui est appelé lorsque la vue est gonflée. AndroidView se recompose chaque fois qu'une lecture State du rappel change. Comme de nombreux autres composables intégrés, AndroidView utilise un paramètre Modifier qui peut servir, par exemple, à définir sa position dans le composable parent.

@Composable
fun CustomView() {
    var selectedItem by remember { mutableStateOf(0) }

    // Adds view to Compose
    AndroidView(
        modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
        factory = { context ->
            // Creates view
            MyView(context).apply {
                // Sets up listeners for View -> Compose communication
                setOnClickListener {
                    selectedItem = 1
                }
            }
        },
        update = { view ->
            // View's been inflated or state read in this block has been updated
            // Add logic here if necessary

            // As selectedItem is read here, AndroidView will recompose
            // whenever the state changes
            // Example of Compose -> View communication
            view.selectedItem = selectedItem
        }
    )
}

@Composable
fun ContentExample() {
    Column(Modifier.fillMaxSize()) {
        Text("Look at this CustomView!")
        CustomView()
    }
}

AndroidView avec liaison de vue

Pour intégrer une mise en page XML, utilisez l'API AndroidViewBinding fournie par la bibliothèque androidx.compose.ui:ui-viewbinding. Pour ce faire, votre projet doit activer la liaison de vue.

@Composable
fun AndroidViewBindingExample() {
    AndroidViewBinding(ExampleLayoutBinding::inflate) {
        exampleView.setBackgroundColor(Color.GRAY)
    }
}

AndroidView dans les listes différées

Si vous utilisez un AndroidView dans une liste paresseuse (LazyColumn, LazyRow, Pager, etc.), envisagez d'utiliser la surcharge AndroidView introduite dans la version 1.4.0-rc01. Cette surcharge permet à Compose de réutiliser l'instance View sous-jacente lorsque la composition contenante est réutilisée, comme c'est le cas pour les listes différées.

Cette surcharge de AndroidView ajoute deux paramètres supplémentaires:

  • onReset : rappel appelé pour signaler que l'View est sur le point d'être réutilisé. Cette valeur ne doit pas être nulle pour permettre la réutilisation de la vue.
  • onRelease (facultatif) : rappel appelé pour signaler que le View a quitté la composition et ne sera plus réutilisé.

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun AndroidViewInLazyList() {
    LazyColumn {
        items(100) { index ->
            AndroidView(
                modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
                factory = { context ->
                    MyView(context)
                },
                update = { view ->
                    view.selectedItem = index
                },
                onReset = { view ->
                    view.clear()
                }
            )
        }
    }
}

Fragments dans Compose

Utilisez le composable AndroidViewBinding pour ajouter un Fragment dans Compose. AndroidViewBinding offre une gestion spécifique au fragment, comme la suppression du fragment lorsque le composable quitte la composition.

Pour ce faire, gonflez le code XML d'un conteneur FragmentContainerView pour en faire le conteneur de votre Fragment.

Par exemple, si vous avez défini my_fragment_layout.xml, vous pouvez utiliser un code comme celui-ci tout en remplaçant l'attribut XML android:name par le nom de classe de Fragment :

<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="com.example.compose.snippets.interop.MyFragment" />

Pour gonfler ce fragment dans Compose, procédez comme suit :

@Composable
fun FragmentInComposeExample() {
    AndroidViewBinding(MyFragmentLayoutBinding::inflate) {
        val myFragment = fragmentContainerView.getFragment<MyFragment>()
        // ...
    }
}

Si vous devez utiliser plusieurs fragments dans la même mise en page, assurez-vous d'avoir défini un ID unique pour chaque FragmentContainerView.

Appeler le framework Android à partir de Compose

Compose fonctionne dans les classes du framework Android. Par exemple, il est hébergé sur les classes de vues Android, comme Activity ou Fragment, et peut utiliser des classes du framework Android comme Context, les ressources système, Service ou encore BroadcastReceiver.

Pour en savoir plus sur les ressources système, consultez la section Ressources dans Compose.

Compositions locales

Les classes CompositionLocal permettent de transmettre des données implicitement via des fonctions modulables. Elles sont généralement accompagnées d'une valeur dans un nœud spécifique de l'arborescence de l'interface utilisateur. Cette valeur peut être utilisée par ses descendants composables sans déclarer le CompositionLocal en tant que paramètre dans la fonction modulable.

CompositionLocal permet de propager des valeurs pour les types de frameworks Android dans Compose, tels que Context, Configuration ou View, dans lesquels le code Compose est hébergé avec les éléments LocalContext, LocalConfiguration ou LocalView correspondants. Notez que les classes CompositionLocal sont précédées de Local pour une meilleure visibilité avec la saisie semi-automatique dans l'IDE.

Pour accéder à la valeur actuelle de CompositionLocal, utilisez sa propriété current. Par exemple, le code ci-dessous affiche un toast en fournissant le LocalContext.current dans la méthode Toast.makeToast.

@Composable
fun ToastGreetingButton(greeting: String) {
    val context = LocalContext.current
    Button(onClick = {
        Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show()
    }) {
        Text("Greet")
    }
}

Pour un exemple plus complet, consultez la section Étude de cas : BroadcastReceivers à la fin de ce document.

Autres interactions

Si aucun utilitaire n'est défini pour l'interaction dont vous avez besoin, nous vous recommandons de suivre les consignes générales de Compose : le flux de données descend, le flux d'événements monte. Plus de détails sont disponibles dans Raisonnement dans Compose. Par exemple, ce composable lance une autre activité :

class OtherInteractionsActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // get data from savedInstanceState
        setContent {
            MaterialTheme {
                ExampleComposable(data, onButtonClick = {
                    startActivity(Intent(this, MyActivity::class.java))
                })
            }
        }
    }
}

@Composable
fun ExampleComposable(data: DataExample, onButtonClick: () -> Unit) {
    Button(onClick = onButtonClick) {
        Text(data.title)
    }
}

Étude de cas : broadcast receivers

Pour obtenir un exemple plus concret des fonctionnalités que vous pouvez migrer ou implémenter dans ComposeCompositionLocal, ainsi que des effets secondaires, imaginons qu'un BroadcastReceiver doit être enregistré à partir d'une fonction modulable.

La solution utilise LocalContext pour utiliser le contexte actuel, ainsi que les effets secondaires rememberUpdatedState et DisposableEffect.

@Composable
fun SystemBroadcastReceiver(
    systemAction: String,
    onSystemEvent: (intent: Intent?) -> Unit
) {
    // Grab the current context in this part of the UI tree
    val context = LocalContext.current

    // Safely use the latest onSystemEvent lambda passed to the function
    val currentOnSystemEvent by rememberUpdatedState(onSystemEvent)

    // If either context or systemAction changes, unregister and register again
    DisposableEffect(context, systemAction) {
        val intentFilter = IntentFilter(systemAction)
        val broadcast = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                currentOnSystemEvent(intent)
            }
        }

        context.registerReceiver(broadcast, intentFilter)

        // When the effect leaves the Composition, remove the callback
        onDispose {
            context.unregisterReceiver(broadcast)
        }
    }
}

@Composable
fun HomeScreen() {

    SystemBroadcastReceiver(Intent.ACTION_BATTERY_CHANGED) { batteryStatus ->
        val isCharging = /* Get from batteryStatus ... */ true
        /* Do something if the device is charging */
    }

    /* Rest of the HomeScreen */
}

Étapes suivantes

Maintenant que vous connaissez les API d'interopérabilité lorsque vous utilisez Compose dans les vues et inversement, consultez la page Autres considérations pour en savoir plus.