Como integrar o Compose à IU já existente

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Se você tiver um app que usa uma IU com base em visualização, talvez não queira reescrever toda a IU de uma vez só. Esta página ajudará você a adicionar novos elementos do Compose à sua IU já existente.

Como migrar uma IU compartilhada

Caso você esteja migrando gradualmente para o Compose, talvez precise usar elementos de IU compartilhados no Compose e no sistema de visualização. Por exemplo, caso seu app tenha um componente CallToActionButton personalizado, pode ser necessário usá-lo nas telas do Compose e nas baseadas em visualizações.

No Compose, os elementos de IU compartilhados se tornam elementos de composição que podem ser reutilizados em todo o app, não importa se o elemento é estilizado usando XML ou se é uma visualização personalizada. Por exemplo, você pode criar um elemento CallToActionButton composto para o componente Button de chamada para ação personalizado.

Para usar esse elemento em telas baseadas em visualização, é necessário criar um wrapper de visualização personalizado que se estenda de AbstractComposeView. No Content substituído, coloque o elemento composto que você criou incluído no seu tema do Compose, conforme mostrado no exemplo abaixo:

@Composable
fun CallToActionButton(
    text: String,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
    ) {
        Text(text)
    }
}

class CallToActionViewButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : AbstractComposeView(context, attrs, defStyle) {

    var text by mutableStateOf<String>("")
    var onClick by mutableStateOf<() -> Unit>({})

    @Composable
    override fun Content() {
        YourAppTheme {
            CallToActionButton(text, onClick)
        }
    }
}

Observe que os parâmetros compostos se tornam variáveis mutáveis dentro da visualização personalizada. Isso torna a visualização personalizada CallToActionViewButton inflável e utilizável, por exemplo, com vinculação de visualizações, assim como uma visualização tradicional. Veja o exemplo abaixo:

class ExampleActivity : Activity() {

    private lateinit var binding: ActivityExampleBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityExampleBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.callToAction.apply {
            text = getString(R.string.something)
            onClick = { /* Do something */ }
        }
    }
}

Se o componente personalizado tiver um estado mutável, consulte a Fonte de verdade do estado.

Temas

Seguindo o Material Design, o uso da biblioteca Material Design Components for Android (MDC) é a maneira recomendada de aplicar temas a apps Android. Conforme abordado na documentação de temas no Compose, o Compose implementa esses conceitos com a função composta MaterialTheme.

Ao criar novas telas no Compose, aplique um MaterialTheme antes de elementos compostos que emitem a IU da biblioteca Material Design Components. Os componentes do Material Design (Button, Text etc.) dependem da existência de um MaterialTheme, e o comportamento deles fica indefinido sem isso.

Todas as amostras do Jetpack Compose usam um tema personalizado do Compose criado sobre MaterialTheme.

Várias fontes da verdade

Um app provavelmente tem uma grande quantidade de temas e estilos para visualizações. Quando você introduz o Compose em um app existente, é necessário migrar o tema para usar MaterialTheme em qualquer tela do Compose. Isso significa que os temas do seu app terão duas fontes da verdade: o tema baseado na visualização e o tema do Compose. Qualquer mudança no estilo precisa ser feita em vários lugares.

Se você pretende migrar o app totalmente para o Compose, é preciso criar uma versão do tema existente para ele. O problema é que, quanto antes no processo de desenvolvimento você criar o tema do Compose, mais manutenção você precisará fazer durante o desenvolvimento.

Biblioteca MDC Compose Theme Adapter

Se estiver usando a biblioteca MDC no seu app Android, a biblioteca MDC Compose Theme Adapter vai permitir que, nos seus elementos de composição, você reutilize com facilidade as definições de cor, tipografia e forma (links em inglês) dos temas existentes baseados na visualização:

import com.google.android.material.composethemeadapter.MdcTheme

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            // Use MdcTheme instead of MaterialTheme
            // Colors, typography, and shape have been read from the
            // View-based theme used in this Activity
            MdcTheme {
                ExampleComposable(/*...*/)
            }
        }
    }
}

Consulte a documentação da biblioteca MDC para saber mais.

Biblioteca AppCompat Compose Theme Adapter

A biblioteca AppCompat Compose Theme Adapter (link em inglês) permite reutilizar facilmente temas XML da AppCompat para definir temas no Jetpack Compose. Ela cria um MaterialTheme com os valores de cor e tipografia (links em inglês) usando o tema do contexto.

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            AppCompatTheme {
                // Colors, typography, and shape have been read from the
                // View-based theme used in this Activity
                ExampleComposable(/*...*/)
            }
        }
    }
}

Estilos padrão dos componentes

As bibliotecas MDC e AppCompat Compose Theme Adapter não leem nenhum estilo de widget padrão definido pelo tema. Isso ocorre porque o Compose não tem o conceito de funções default compostas.

Saiba mais sobre estilos de componentes e sistemas de design personalizados na documentação de temas.

Sobreposições de tema no Compose

Ao migrar telas baseadas em visualização para o Compose, preste atenção aos usos do atributo android:theme. É provável que você precise de um novo MaterialTheme nessa parte da árvore de IU do Compose.

Leia mais sobre isso no Guia de temas.

Animações de WindowInsets e IME

Desde o Compose 1.2.0, você pode processar WindowInsets usando modificadores para fazer o processamento deles nos seus layouts. Também há suporte para animações do IME (editor de método de entrada, na sigla em inglês).

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
              MyScreen()
            }
        }
    }
}

@Composable
fun MyScreen() {
    Box {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize() // fill the entire window
                .imePadding() // padding for the bottom for the IME
                .imeNestedScroll(), // scroll IME at the bottom
            content = { }
        )
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding() // padding for navigation bar
                .imePadding(), // padding for when IME appears
            onClick = { }
        ) {
            Icon( /* ... */)
        }
    }
}

Animação mostrando um elemento da IU rolando para cima e para baixo para dar lugar ao teclado.

Figura 2. Animações do IME (editor de método de entrada, na sigla em inglês).

Consulte a documentação da biblioteca accompanists-insets para saber mais.

Priorizar a divisão de estado da apresentação

Tradicionalmente, uma View tem um estado. Uma View gerencia campos que descrevem o que exibir, além de como exibir. Ao converter uma View para o Compose, separe os dados renderizados para alcançar um fluxo de dados unidirecional, conforme explicado em mais detalhes na seção sobre elevação de estado.

Por exemplo, uma View tem uma propriedade visibility que descreve se ela está visível, invisível ou se desapareceu. Essa é uma propriedade inerente da View. Outras partes do código podem mudar a visibilidade de uma View, mas somente a própria View realmente sabe qual é sua visibilidade atual. A lógica para garantir que uma View esteja visível pode ser propensa a erros e geralmente está vinculada à View em si.

Por outro lado, o Compose facilita a exibição de elementos que podem ser compostos totalmente diferentes usando a lógica condicional no Kotlin:

if (showCautionIcon) {
    CautionIcon(/* ... */)
}

Por padrão, o CautionIcon não precisa saber ou se importar por que está sendo exibido, e não há o conceito de visibility: ele está na composição ou não.

Ao separar de maneira clara a lógica de gerenciamento de estados e apresentação, você pode mudar mais livremente a forma como o conteúdo é exibido como uma conversão de estado para a IU. A elevação do estado quando necessário também torna os elementos que podem ser compostos mais reutilizáveis, já que a propriedade do estado é mais flexível.

Promover componentes encapsulados e reutilizáveis

Os elementos da View geralmente têm uma ideia do local em que residem: dentro de uma Activity, uma Dialog, um Fragment ou dentro de outra hierarquia de View. Como eles geralmente são inflados dos arquivos de layout estático, a estrutura geral de uma View tende a ser muito rígida. Isso resulta em um acoplamento rígido e dificulta a alteração ou reutilização de uma View.

Por exemplo, uma View personalizada pode presumir que haja uma visualização filha de determinado tipo com determinado ID e mudar as propriedades diretamente em resposta a uma ação. Isso une os elementos da View de forma rígida: a View personalizada poderá falhar ou ser corrompida se não encontrar a filha, que provavelmente não poderá ser reutilizada sem a View mãe personalizada.

Isso é um problema menor no Compose com os elementos que podem ser compostos reutilizáveis. Os pais podem especificar facilmente o estado e os callbacks para que elementos compostos reutilizáveis sejam programados sem a necessidade de saber exatamente onde serão usados.

var isEnabled by rememberSaveable { mutableStateOf(false) }

Column {
    ImageWithEnabledOverlay(isEnabled)
    ControlPanelWithToggle(
        isEnabled = isEnabled,
        onEnabledChanged = { isEnabled = it }
    )
}

No exemplo acima, as três partes estão mais encapsuladas e menos acopladas:

  • A ImageWithEnabledOverlay só precisa saber qual é o estado atual de isEnabled. Não é necessário saber que o ControlPanelWithToggle existe nem como pode ser controlado.

  • O ControlPanelWithToggle não sabe que a ImageWithEnabledOverlay existe. Poderia haver zero, uma ou mais maneiras de exibir isEnabled, e o ControlPanelWithToggle não precisaria ser mudado.

  • Para o pai, não importa o nível de aninhamento de ImageWithEnabledOverlay ou ControlPanelWithToggle. Esses filhos podem animar mudanças, trocar ou transmitir conteúdo para outros filhos.

Esse padrão é conhecido como inversão de controle. Leia mais sobre isso na documentação de CompositionLocal.

Como gerenciar mudanças no tamanho da tela

Ter recursos diferentes para tamanhos de janela diferentes é uma das principais maneiras de criar layouts de View responsivos. Embora os recursos qualificados ainda sejam uma opção para decisões de layout na tela, o Compose facilita a mudança completa de layouts em códigos com a lógica condicional normal. Usando ferramentas como BoxWithConstraints, é possível tomar decisões com base no espaço disponível para elementos individuais, o que não é possível com recursos qualificados:

@Composable
fun MyComposable() {
    BoxWithConstraints {
        if (minWidth < 480.dp) {
            /* Show grid with 4 columns */
        } else if (minWidth < 720.dp) {
            /* Show grid with 8 columns */
        } else {
            /* Show grid with 12 columns */
        }
    }
}

Leia sobre como criar layouts adaptáveis para aprender as técnicas que o Compose oferece para criar IUs adaptáveis.

Rolagem aninhada com visualizações

Para mais informações sobre como ativar a interoperabilidade de rolagem aninhada entre elementos de visualização e de composição roláveis, aninhados em ambas as direções, consulte Interoperabilidade de rolagem aninhada.

Compose na RecyclerView

Os elementos de composição na RecyclerView têm uma boa performance desde a versão 1.3.0-alpha02. Confira se você tem pelo menos a versão 1.3.0-alpha02 da RecyclerView para ter esses benefícios.