Lors de l'adoption de Compose dans votre application, les interfaces utilisateur basées sur Compose et les vues peuvent être combinées. Voici une liste d'API, de recommandations et de conseils pour faciliter la transition vers Compose.
Compose dans les vues
Vous pouvez ajouter une UI basée sur Compose à une application existante qui utilise un design basé sur les vues.
Pour créer un écran entièrement basé sur Compose, demandez à votre activité d'appeler la méthode setContent()
et de transmettre toutes les fonctions modulables de votre choix.
class ExampleActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // In here, we can call composables! MaterialTheme { Greeting(name = "compose") } } } } @Composable fun Greeting(name: String) { Text(text = "Hello $name!") }
Ce code ressemble à ce que vous trouveriez dans une application Compose.
ViewCompositionStrategy pour ComposeView
Par défaut, Compose supprime la composition dès lors que la View est dissociée d'une fenêtre. Les types de View
de l'interface utilisateur Compose, comme ComposeView
et AbstractComposeView
, utilisent un élément ViewCompositionStrategy
qui définit ce comportement.
Par défaut, Compose utilise la stratégie DisposeOnDetachedFromWindowOrReleasedFromPool
. Cependant, cette valeur par défaut est parfois indésirable, notamment lorsque les types de View
de l'UI Compose sont utilisés dans les éléments suivants :
Les fragments. Pour conserver l'état, la composition doit suivre le cycle de vie de la vue du fragment pour les types de
View
de l'UI Compose.Les transitions. Chaque fois que la
View
de l'UI Compose est utilisée dans le cadre d'une transition, elle est détachée de sa fenêtre au début de la transition et non à la fin : votre composable est alors supprimé alors qu'il s'affiche à l'écran.Votre propre
View
personnalisée gérée par le cycle de vie.
Dans certaines de ces situations, l'application peut être sujette à des fuites de mémoire progressives au niveau des instances de composition, sauf si vous appelez AbstractComposeView.disposeComposition
manuellement.
Vous pouvez supprimer automatiquement les compositions qui ne sont plus nécessaires en définissant une autre stratégie, ou en créant la vôtre en appelant la méthode setViewCompositionStrategy
. Par exemple, la stratégie DisposeOnLifecycleDestroyed
supprime la composition lorsque le lifecycle
est détruit. Cette stratégie convient aux types View
d'interface utilisateur Compose qui partagent une relation de type 1:1 avec un LifecycleOwner
connu.
Lorsque LifecycleOwner
n'est pas connu, vous pouvez utiliser DisposeOnViewTreeLifecycleDestroyed
.
Consultez ComposeView dans les fragments pour découvrir comment fonctionne cette API.
ComposeView dans les fragments
Si vous souhaitez intégrer le contenu de l'interface utilisateur Compose dans un fragment ou une mise en page "View" existante, utilisez ComposeView
et appelez sa méthode setContent()
. ComposeView
est une View
Android.
Vous pouvez placer la ComposeView
dans votre mise en page XML comme n'importe quelle autre View
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/hello_world"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello Android!" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Dans le code source en Kotlin, gonflez la mise en page à partir de la ressource de mise en page définie en XML. Obtenez ensuite ComposeView
à l'aide de l'ID XML, définissez la stratégie de composition la plus adaptée pour l'hôte View
, puis appelez setContent()
pour utiliser Compose.
class ExampleFragment : Fragment() { private var _binding: FragmentExampleBinding? = null // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentExampleBinding.inflate(inflater, container, false) val view = binding.root binding.composeView.apply { // Dispose of the Composition when the view's LifecycleOwner // is destroyed setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { // In Compose world MaterialTheme { Text("Hello Compose!") } } } return view } override fun onDestroyView() { super.onDestroyView() _binding = null } }
Figure 1 : Cette image montre la sortie du code qui ajoute des éléments Compose dans une hiérarchie d'UI. Un widget TextView
affiche le message texte "Hello Android!". Un élément de texte Compose affiche le texte "Hello Compose!".
Vous pouvez également inclure une ComposeView
directement dans un fragment si votre mode plein écran est conçu avec Compose, ce qui vous évite d'utiliser un fichier de mise en page XML.
class ExampleFragmentNoXml : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return ComposeView(requireContext()).apply { // Dispose of the Composition when the view's LifecycleOwner // is destroyed setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { MaterialTheme { // In Compose world Text("Hello Compose!") } } } } }
Plusieurs ComposeView dans la même mise en page
S'il existe plusieurs éléments ComposeView
dans la même mise en page, chacun doit disposer d'un ID unique pour que savedInstanceState
fonctionne correctement.
class ExampleFragmentMultipleComposeView : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View = LinearLayout(requireContext()).apply { addView( ComposeView(requireContext()).apply { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) id = R.id.compose_view_x // ... } ) addView(TextView(requireContext())) addView( ComposeView(requireContext()).apply { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) id = R.id.compose_view_y // ... } ) } }
Les ID ComposeView
sont définis dans le fichier res/values/ids.xml
:
<resources>
<item name="compose_view_x" type="id" />
<item name="compose_view_y" type="id" />
</resources>
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() } }
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) } }
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_height="match_parent"
android:layout_width="match_parent"
android:name="com.example.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 View Android, comme Activity
ou Fragment
, et peut avoir besoin d'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 documentation Ressources disponibles 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 : BroadcastReceivers
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 */ }