Source: https://milehighhearing.com/blog/communication-a-two-way-street

startActivityForResult e requestPermissions no Jetpack Compose

Nelson Glauber
4 min readMar 17, 2021

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:

startActivityForResult no Jetpack Compose

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 respectivo Bitmap.
  • TakePicture tira uma foto em uma determinada Uri e retorna um Boolean indicando se a imagem foi salva.
  • TakeVideo grava um vídeo em uma determinada Uri e retorna um Bitmap do thumbnail do vídeo gerado.
  • PickContact abre a lista de contatos do aparelho e retorna a Uri do contato selecionado.
  • GetContent ou GetMultipleContents exibe para o usuário a opção para selecionar um conteúdo genérico obtido via content://.
  • OpenDocument ou OpenDocuments 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 a Uri 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

--

--

Nelson Glauber
Nelson Glauber

Written by Nelson Glauber

Android Developer. Google Developer Expert for Android/Kotlin. Author of “Dominando o Android”.

No responses yet