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 quando o elemento combinável é removido da composição.

Resultado da atividade

A API rememberLauncherForActivityResult() permite que você receba o resultado de uma atividade no elemento combinável:

@Composable
fun GetContentExample() {
    var imageUri by remember { mutableStateOf<Uri?>(null) }
    val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
        imageUri = uri
    }
    Column {
        Button(onClick = { launcher.launch("image/*") }) {
            Text(text = "Load Image")
        }
        Image(
            painter = rememberAsyncImagePainter(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 das permissões no estado que a interface 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 combinável, 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 estiver ativado.

ViewModel

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

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

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = 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 o elemento combinável for usado em uma atividade, a função viewModel() vai retornar a mesma instância até que a atividade seja concluída ou o processo seja encerrado.

class MyViewModel : ViewModel() { /*...*/ }
// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    // Returns the same instance as long as the activity is alive,
    // just as if you grabbed the instance from an Activity or Fragment
    viewModel: MyViewModel = viewModel()
) { /* ... */ }

@Composable
fun MyScreen2(
    viewModel: MyViewModel = viewModel() // Same instance as in MyScreen
) { /* ... */ }

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 Navigation 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.

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    val dataExample = viewModel.exampleLiveData.observeAsState()

    // Because the state is read here,
    // MyScreen 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.

O componente de navegação oferece suporte a aplicativos do Jetpack Compose. Consulte Como navegar com o Compose e Migrar a navegação do Jetpack para o Navigation Compose para ver mais informações.

Hilt

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

A função viewModel() mencionada na seção ViewModel usa automaticamente 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 MyViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle,
    private val repository: ExampleRepository
) : ViewModel() { /* ... */ }

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) { /* ... */ }

Hilt e navegação

O Hilt também se integra à biblioteca Navigation Compose. Adicione as seguintes dependências a mais ao arquivo do Gradle:

Groovy

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

Kotlin

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

Ao usar a biblioteca Navigation Compose, sempre use a função de composição hiltViewModel para acessar uma instância do ViewModel anotado com @HiltViewModel. Isso funciona com fragmentos ou atividades que incluem a anotação @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() {
    val navController = rememberNavController()
    val startRoute = "example"
    NavHost(navController, startDestination = startRoute) {
        composable("example") { backStackEntry ->
            // Creates a ViewModel from the current BackStackEntry
            // Available in the androidx.hilt:hilt-navigation-compose artifact
            val viewModel = hiltViewModel<MyViewModel>()
            MyScreen(viewModel)
        }
        /* ... */
    }
}

Se você precisar recuperar a instância de um ViewModel com escopo para rotas de navegação ou o gráfico de navegação, use a função de composição hiltViewModel e transmita a backStackEntry correspondente como um parâmetro:

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

@Composable
fun MyApp() {
    val navController = rememberNavController()
    val startRoute = "example"
    val innerStartRoute = "exampleWithRoute"
    NavHost(navController, startDestination = startRoute) {
        navigation(startDestination = innerStartRoute, route = "Parent") {
            // ...
            composable("exampleWithRoute") { backStackEntry ->
                val parentEntry = remember(backStackEntry) {
                    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.

Confira um exemplo das APIs do Compose da biblioteca Paging:

@Composable
fun MyScreen(flow: Flow<PagingData<String>>) {
    val lazyPagingItems = flow.collectAsLazyPagingItems()
    LazyColumn {
        items(
            lazyPagingItems.itemCount,
            key = lazyPagingItems.itemKey { it }
        ) { index ->
            val item = lazyPagingItems[index]
            Text("Item is $item")
        }
    }
}

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

Mapas

Você pode usar a biblioteca do Maps Compose para oferecer o Google Maps no seu app. Veja um exemplo de uso:

@Composable
fun MapsExample() {
    val singapore = LatLng(1.35, 103.87)
    val cameraPositionState = rememberCameraPositionState {
        position = CameraPosition.fromLatLngZoom(singapore, 10f)
    }
    GoogleMap(
        modifier = Modifier.fillMaxSize(),
        cameraPositionState = cameraPositionState
    ) {
        Marker(
            state = MarkerState(position = singapore),
            title = "Singapore",
            snippet = "Marker in Singapore"
        )
    }
}