Compose e outras bibliotecas

Você pode usar suas bibliotecas favoritas no Compose. Nesta seção, descrevemos como incorporar algumas das bibliotecas mais úteis.

Atividade

Para usar o Compose em uma atividade, use a ComponentActivity, uma subclasse de Activity que fornece o LifecycleOwner e os componentes adequados para o Compose. Ela fornece também outras APIs que separam o código da substituição de métodos na sua classe de atividade. O Activity Compose expõe essas APIs a elementos que podem ser compostos, de modo que a substituição de métodos fora desses elementos ou a recuperação de uma instância Activity explícita não seja mais necessária. Além disso, as APIs garantem que elas sejam inicializadas apenas uma vez, sobrevivam à recomposição e sejam apagadas adequadamente se o elemento que pode ser composto for removido da composição.

Resultado da atividade

A API rememberLauncherForActivityResult() permite que você receba o resultado de uma atividade no elemento que pode ser composto:

@Composable
fun GetContentExample() {
    var imageUri by remember { mutableStateOf<Uri?>(null) }
    val launcher = rememberLauncherForActivityResult(GetContent()) { uri: Uri? ->
        imageUri = uri
    }
    Column {
        Button(onClick = { launcher.launch("image/*") }) {
            Text(text = "Load Image")
        }
        Image(
            painter = rememberImagePainter(imageUri),
            contentDescription = "My Image"
        )
    }
}

O exemplo demonstra um contrato GetContent() simples. A solicitação será iniciada quando você tocar no botão. O lambda final de rememberLauncherForActivityResult() será invocado quando o usuário selecionar uma imagem e retornar à atividade de inicialização. Essa ação carrega a imagem selecionada usando a função rememberImagePainter() da Coil.

Qualquer subclasse de ActivityResultContract pode ser usada como primeiro argumento de rememberLauncherForActivityResult(). Ou seja, é possível usar essa técnica para solicitar conteúdo do framework e em outros padrões comuns. Com essa técnica, também é possível criar e usar seus próprios contratos personalizados.

Como solicitar permissões de execução

A mesma API Activity Result e rememberLauncherForActivityResult() explicadas acima podem ser usadas para solicitar permissões de execução usando o contrato RequestPermission para uma única permissão ou o contrato RequestMultiplePermissions para várias permissões.

A biblioteca Accompanist Permissions também pode ser usada em uma camada acima dessas APIs para mapear o estado concedido atual de permissões no estado que sua IU do Compose pode usar.

Como processar o botão "Voltar" do sistema

Para fornecer uma navegação de retorno personalizada e substituir o comportamento padrão do botão "Voltar" do sistema no elemento que pode ser composto, o elemento pode usar um BackHandler para interceptar o evento:

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

O primeiro argumento controla se o BackHandler está ativado. Você pode usá-lo para desativar temporariamente o processador com base no estado do componente. O lambda final será invocado se o usuário acionar um evento de retorno do sistema enquanto o BackHandler está ativado.

ViewModel

Se você usar a biblioteca Architecture Components ViewModel, poderá acessar um ViewModel em qualquer elemento que pode ser composto chamando a função viewModel().

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

@Composable
fun MyExample(
    viewModel: ExampleViewModel = viewModel()
) {
    // use viewModel here
}

A viewModel() retorna um ViewModel já existente ou cria um novo no escopo especificado. O ViewModel será mantido enquanto o escopo estiver ativo. Por exemplo, se a função que pode ser composta for usada em uma atividade, viewModel() retornará a mesma instância até que a atividade seja concluída ou o processo seja encerrado.

@Composable
fun MyExample(
    // Returns the same instance as long as the activity is alive,
    // just as if you grabbed the instance from an Activity or Fragment
    viewModel: ExampleViewModel = viewModel()
) { /* ... */ }

@Composable
fun MyExample2(
    viewModel: ExampleViewModel = viewModel() // Same instance as in MyExample
) { /* ... */ }

Caso seu ViewModel tenha dependências, viewModel() usará um ViewModelProvider.Factory opcional como parâmetro.

Para ver mais informações sobre ViewModel no Compose e sobre como as instâncias são usadas com a biblioteca de navegação do Compose ou sobre atividades e fragmentos, consulte os documentos sobre interoperabilidade.

Streams de dados

O Compose vem com extensões para as soluções mais populares com base em stream do Android. Cada uma dessas extensões é fornecida por um artefato diferente:

Esses artefatos são registrados como listeners e representam os valores como State. Sempre que um novo valor é emitido, o Compose faz a recomposição das partes da IU em que state.value é usado. Por exemplo, neste código, ShowData faz a recomposição todas as vezes que exampleLiveData emite um novo valor.

@Composable
fun MyExample(
    viewModel: ExampleViewModel = viewModel()
) {
    val dataExample = viewModel.exampleLiveData.observeAsState()

    // Because the state is read here,
    // MyExample recomposes whenever dataExample changes.
    dataExample.value?.let {
        ShowData(dataExample)
    }
}

Operações assíncronas no Compose

O Jetpack Compose permite executar operações assíncronas usando corrotinas em funções que podem ser compostas.

Consulte as APIs LaunchedEffect, produceState e rememberCoroutineScope na documentação de efeitos colaterais para mais informações.

Recomendamos o uso da biblioteca de navegação Compose para adicionar elementos de navegação aos seus projetos no Compose. Esses elementos permitem que você adicione a IU para navegar entre os elementos que podem ser compostos, enquanto aproveita a infraestrutura e os recursos do componente de navegação.

Para saber mais sobre essa integração, consulte a documentação Como navegar com o Compose.

Hilt

O Hilt é a solução recomendada para injeção de dependência em apps para Android e funciona perfeitamente com o Compose.

A função viewModel() mencionada na seção ViewModel automaticamente usa o ViewModel criado pelo Hilt, com a anotação @HiltViewModel. Disponibilizamos uma documentação com informações sobre a integração do ViewModel do Hilt.

@HiltViewModel
class ExampleViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle,
    private val repository: ExampleRepository
) : ViewModel() { /* ... */ }

@Composable
fun ExampleScreen(
    exampleViewModel: ExampleViewModel = viewModel()
) { /* ... */ }

Hilt e navegação

O Hilt também se integra à biblioteca de navegação do Compose. Adicione estas outras dependências ao arquivo do Gradle:

Groovy

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

Kotlin

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

Se o ViewModel anotado com @HiltViewModel estiver com escopo definido para o gráfico de navegação, use a função que pode ser composta do hiltViewModel que funciona com fragmentos ou atividades que são anotados com @AndroidEntryPoint.

Por exemplo, se ExampleScreen for um destino em um gráfico de navegação, chame hiltViewModel() para ter uma instância de ExampleViewModel com escopo para o destino, como mostrado no snippet de código abaixo:

// import androidx.hilt.navigation.compose.hiltViewModel

@Composable
fun MyApp() {
    NavHost(navController, startDestination = startRoute) {
        composable("example") { backStackEntry ->
            // Creates a ViewModel from the current BackStackEntry
            // Available in the androidx.hilt:hilt-navigation-compose artifact
            val exampleViewModel = hiltViewModel<ExampleViewModel>()
            ExampleScreen(exampleViewModel)
        }
        /* ... */
    }
}

Se você precisar recuperar a instância de um ViewModel com escopo para rotas de navegação, transmita a raiz de destino como um parâmetro:

// import androidx.hilt.navigation.compose.hiltViewModel
// import androidx.navigation.compose.getBackStackEntry

@Composable
fun MyApp() {
    NavHost(navController, startDestination = startRoute) {
        navigation(startDestination = innerStartRoute, route = "Parent") {
            // ...
            composable("exampleWithRoute") { backStackEntry ->
                val parentEntry = remember {
                  navController.getBackStackEntry("Parent")
                }
                val parentViewModel = hiltViewModel<ParentViewModel>(
                  parentEntry
                )
                ExampleWithRouteScreen(parentViewModel)
            }
        }
    }
}

Paging

A biblioteca Paging facilita o carregamento gradual de dados e é compatível com o Compose. A página de lançamento da Paging contém informações sobre a dependência complementar paging-compose, que precisa ser adicionada ao projeto e à respectiva versão.

Veja um exemplo das APIs do Compose da biblioteca Paging:

@Composable
fun MyExample(flow: Flow<PagingData<String>>) {
    val lazyPagingItems = flow.collectAsLazyPagingItems()
    LazyColumn {
        items(lazyPagingItems) {
            Text("Item is $it")
        }
    }
}

Consulte a documentação de listas para ver mais informações sobre como usar a Paging no Compose.

Carregamento de imagem

A biblioteca Coil (link em inglês) da Instacart oferece funções que podem ser compostas para carregar imagens de fontes externas, como o carregamento de imagens remotas pela rede.

Veja um exemplo de rememberImagePainter do artefato io.coil-kt:coil-compose:

@Composable
fun MyExample() {
    val painter = rememberImagePainter(
        data = "https://picsum.photos/300/300",
        builder = {
            crossfade(true)
        }
    )

    Box {
        Image(
            painter = painter,
            contentDescription = stringResource(R.string.image_content_desc),
        )

        when (painter.state) {
            is ImagePainter.State.Loading -> {
                // Display a circular progress indicator whilst loading
                CircularProgressIndicator(Modifier.align(Alignment.Center))
            }
            is ImagePainter.State.Error -> {
                // If you wish to display some content if the request fails
            }
        }
    }
}