Como usar visualizações no Compose

É possível incluir uma hierarquia de visualização do Android em uma IU do Compose. Essa abordagem vai ser útil principalmente se você quiser usar elementos da interface que ainda não estão disponíveis no Compose, como AdView. Essa abordagem também permite reutilizar visualizações personalizadas que você pode ter criado.

Para incluir um elemento ou uma hierarquia de visualização, use o elemento combinável AndroidView . AndroidView recebe uma lambda que retorna uma View. AndroidView também fornece um callback update que é chamado quando a visualização é inflada. A AndroidView faz a recomposição sempre que um State lido dentro do callback muda. A AndroidView, assim como vários outros elementos combináveis integrados, aceita um parâmetro Modifier que pode ser usado, por exemplo, para definir a posição dela no elemento combinável pai.

@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 com vinculação de visualizações

Para incorporar um layout XML, use a API AndroidViewBinding, que é fornecida pela biblioteca androidx.compose.ui:ui-viewbinding. Para isso, seu projeto precisa ativar a vinculação de visualizações.

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

AndroidView em listas lentas

Se você estiver usando um AndroidView em uma lista Lazy (LazyColumn, LazyRow, Pager etc.), use a sobrecarga AndroidView introduzida na versão 1.4.0-rc01. Essa sobrecarga permite que o Compose reutilize a instância View subjacente quando a composição que contém é reutilizada, como é o caso das listas Lazy.

Essa sobrecarga de AndroidView adiciona dois parâmetros:

  • onReset: um callback invocado para sinalizar que o View está prestes a ser reutilizado. Ele precisa ser diferente de "null" para permitir o reuso da visualização.
  • onRelease (opcional): um callback invocado para sinalizar que o View saiu da composição e não será reutilizado.

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

Fragmentos no Compose

Use o elemento combinável AndroidViewBinding para adicionar um Fragment no Compose. O AndroidViewBinding inclui processamentos específicos de fragmentos, como remover o fragmento quando o elemento combinável sai da composição.

Para isso, infle o XML contendo uma FragmentContainerView como detentora do Fragment.

Por exemplo, se você tiver o my_fragment_layout.xml definido, poderá usar um código como este ao substituir o atributo android:name do XML pelo nome da 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" />

Infle esse fragmento no Compose desta maneira:

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

Se você precisa usar vários fragmentos no mesmo layout, defina um ID exclusivo para cada FragmentContainerView.

Como chamar o framework do Android no Compose

O Compose opera dentro das classes do framework do Android. Por exemplo, ele é hospedado em classes de visualização do Android, como Activity ou Fragment, e pode precisar usar classes do framework do Android, como Context, recursos do sistema, Service ou BroadcastReceiver.

Para saber mais sobre recursos do sistema, consulte Recursos no Compose.

Classes Composition Locals

As classes CompositionLocal permitem transmitir dados implicitamente usando funções combináveis. Em geral, elas recebem um valor em determinado nó da árvore da interface. Esse valor pode ser usado pelos descendentes combináveis sem declarar o CompositionLocal como um parâmetro na função combinável.

O CompositionLocal é usado na propagação de valores para tipos de framework do Android no Compose (como Context, Configuration ou View) em que o código do Compose é hospedado com o LocalContext, LocalConfiguration ou LocalView correspondente. As classes CompositionLocal têm o prefixo Local para melhor detecção do dispositivo com o preenchimento automático no ambiente de desenvolvimento integrado.

Acesse o valor atual de um CompositionLocal usando a propriedade current. Por exemplo, o código abaixo mostra uma mensagem de aviso usando LocalContext.current no método Toast.makeToast.

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

Para ver um exemplo mais completo, confira a seção Estudo de caso: BroadcastReceivers no fim deste documento.

Outras interações

Caso não haja um utilitário definido para a interação necessária, a prática recomendada é seguir as diretrizes gerais do Compose (o fluxo de dados desce, os eventos sobem), discutidas com mais detalhes em Como trabalhar com o Compose. Por exemplo, este elemento combinável inicia uma atividade diferente:

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

Estudo de caso: broadcast receivers

Para ver um exemplo mais realista dos recursos que você quer migrar ou implementar no Compose e para demonstrar o CompositionLocal e os efeitos colaterais, digamos que um BroadcastReceiver precise ser registrado usando uma função combinável.

A solução utiliza LocalContext para usar o contexto atual e os efeitos colaterais de rememberUpdatedState e 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 */
}

Próximas etapas

Agora que você conhece as APIs de interoperabilidade ao usar o Compose nas visualizações e vice-versa, acesse a página Outras considerações para saber mais.