startActivityForResult e requestPermissions no Jetpack Compose
Com a constante evolução do Jetpack Compose, vários cenários vêem aparecendo à medida que vou testando esse novo toolkit. E uma situação muito comum é realizar a integração da aplicação com outras bibliotecas e frameworks. Me deparei com a situação em que era necessário iniciar uma activity para obter uma determinada informação. Como fazer isso no Jetpack Compose? Veremos isso nesse post curtinho 😉
Solicitando dados de outra activity
Primeiramente é preciso definir uma subclasse de ActivityResultContract<I, O>
. Essa classe classe está disponível na biblioteca androidx.activity
.
class MyActivityResultContract :
ActivityResultContract<Int, String>() { override fun createIntent(
context: Context, input: Int?
): Intent {
return Intent(context, ResponseActivity::class.java).apply {
putExtra(EXTRA_INT, input)
}
}
override fun parseResult(
resultCode: Int, intent: Intent?
): String {
return if (resultCode == Activity.RESULT_OK) {
return intent?.getStringExtra(EXTRA_STRING)
?: "Empty result"
} else {
"Nothing selected"
}
}
}
Essa classe recebe dois tipos genéricos: o primeiro é o parâmetro que se deseja passar para a Activity (via Bundle
/ extras); e o segundo tipo é o que a activity retornará para a função composable. Nesse exemplo a Activity receberá um Int
e retornará uma String
.
A função createIntent
como o próprio nome indica, instancia e retorna o objeto Intent
que será utilizado para iniciar a activity. Enquanto que a função parseResult
tratará o resultado retornado pela activity.
Observe agora a ResponseActivity
que retornará os dados…
class ResponseActivity :
AppCompatActivity(R.layout.activity_response) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
findViewById<Button>(R.id.btn1)
.setOnClickListener {
sendActivityResult("Button 1")
}
findViewById<Button>(R.id.btn2)
.setOnClickListener {
sendActivityResult("Button 2")
}
findViewById<Button>(R.id.btn3)
.setOnClickListener {
sendActivityResult("Button 3")
}
}
private fun sendActivityResult(text: String) {
val anInt = intent.getIntExtra(EXTRA_INT, -1)
setResult(RESULT_OK, Intent().apply {
putExtra(EXTRA_STRING, "$text: $anInt")
})
finish()
}
companion object {
const val EXTRA_INT = "anInt"
const val EXTRA_STRING = "anString"
}
}
Nada de muito especial aqui… Essa activity possui três botões, onde cada um deles invoca a função sendActivityResult
passando um texto diferente como parâmetro. Essa função, por sua vez, retorna os dados para a activity de origem usando o tradicional método setResult
.
A implementação da função composable fica bem simplificada:
@Composable
fun GetActivityResultScreen() {
var textResult by rememberSaveable { mutableStateOf("") }
val launcher = rememberLauncherForActivityResult(
MyActivityResultContract()
) { text ->
textResult = text
}
Column {
Button(onClick = {
launcher.launch(Random.nextInt(0, 100))
}) {
Text("Launch Activity")
}
Text(textResult)
}
}
O textResult
armazenará o retorno da activity e inicia com o valor vazio. A função rememberSaveable
armazena os valores mesmo após girar a tela, o que é bem útil. Para iniciar a activity é usada a função rememberLauncherForActivityResult
que recebe como parâmetro um ActivityResultContract
e retorna um objeto ActivityResultLauncher
. Essa função está sendo chamada passando uma instância do MyActivityResultContract
e perceba que no lambda é obtido o resultado da chamada da activity. Com esse resultado, o estado textResult
é atualizado.
Finalmente, no evento de clique do botão, a função launch
do objeto launcher
(do tipo ActivityResultLauncher
) é chamada para iniciar a activity. Nesse exemplo é passado um número aleatório apenas à título de demonstração.
O resultado pode ser visto a seguir:
Para todas as Intents eu tenho que fazer isso?
Não! Eu demonstrei a API base para obter informações de outra activity, mas o Google já implementou algumas para nós. Na classe ActivityResultContracts
já temos as seguintes ações:
TakePicturePreview
tira apenas uma miniatura de uma foto e retorna o respectivoBitmap
.TakePicture
tira uma foto em uma determinadaUri
e retorna umBoolean
indicando se a imagem foi salva.TakeVideo
grava um vídeo em uma determinadaUri
e retorna umBitmap
do thumbnail do vídeo gerado.PickContact
abre a lista de contatos do aparelho e retorna aUri
do contato selecionado.GetContent
ouGetMultipleContents
exibe para o usuário a opção para selecionar um conteúdo genérico obtido viacontent://
.OpenDocument
ouOpenDocuments
exibe para o usuário a opção para selecionar um (ou vários) documentos de um (ou vários tipos).CreateDocument
permite o usuário criar um determinado documento. Recebe como parâmetro o nome do documento e retorna aUri
do documento criado.
Lembrando que para todas essas ações você deve ter as devidas permissões.
Requisitando permissões
Por sinal, as permissões podem ser requisitadas utilizando a mesma abordagem:
val permissionGranted = ContextCompat.checkSelfPermission(
LocalContext.current,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
var hasPermission by rememberSaveable {
mutableStateOf(permissionGranted)
}
val permissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
hasPermission = granted
}if (hasPermission) {
Text("Permission granted!")
} else {
Button(onClick = {
permissionLauncher.launch(Manifest.permission.CAMERA)
}) {
Text("Request Permission")
}
}
Inicialmente é verificado se a aplicação possui a permissão de acesso à câmera. Em seguida, o estado hasPermission
é definido para permitir a recomposição/atualização do componente quando a permissão for concedida. Então a função registerActivityForResult
foi utilizada novamente, mas dessa vez passando o objeto RequestPermission
, que determina que se a permissão for concedida, o parâmetro granted
do lambda será verdadeiro, caso a permissão tenha sido negada, ele será falso.
Por fim, caso precise solicitar mais de uma permissão, basta utilizar o objeto RequestMultiplePermissions
. Ele recebe como parâmetro (no launch
) um array de strings contendo as permissões desejadas e retorna um Map<String,Boolean>
onde a chave é a permissão e o valor indica se aquela permissão foi concedida ou não.
Espero que tenham gostado e qualquer dúvida, é só deixar o seu comentário.
4br4ç05
nglauber