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 de 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(). Adicione a dependência abaixo ao arquivo do Gradle:

Groovy

dependencies {
    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1'
}

Kotlin

dependencies {
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
}

Você pode usar a função viewModel() no seu código.

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

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

viewModel() retorna um ViewModel existente ou cria um novo. Por padrão, o ViewModel retornado tem o escopo definido para a atividade, fragmento ou destino de navegação que o envolvem e é mantido enquanto o escopo estiver ativo.

Por exemplo, se o elemento combinável for usado em uma atividade, 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
) { /* ... */ }

Diretrizes de uso

Geralmente, as instâncias de ViewModel são acessadas em elementos combináveis no nível da tela, ou seja, perto de um elemento raiz chamado de uma atividade, um fragmento ou um destino de um gráfico de navegação. Isso ocorre porque os ViewModels têm, por padrão, o escopo desses objetos no nível da tela. Leia mais sobre o ciclo de vida e o escopo de um ViewModel.

Evite transmitir instâncias de ViewModel para outros elementos combináveis, já que isso pode dificultar o teste deles e corromper as visualizações. Em vez disso, transmita apenas os dados e as funções de que precisam como parâmetros.

Você pode usar instâncias ViewModel para gerenciar o estado de elementos combináveis no nível da subtela. No entanto, esteja ciente do ciclo de vida e escopo da ViewModel. Se o elemento combinável for autossuficiente, use o Hilt para injetar o ViewModel e evitar a necessidade de transmitir dependências de elementos combináveis pais.

Se o ViewModel tiver 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. Para mais informações, consulte Como navegar com o Compose e Migrar a navegação do Jetpack para o Navigation Compose.

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