1. Antes de começar
Neste codelab, você vai aprender a implementar o recurso Fazer login com o Google no Android usando o Credential Manager.
Pré-requisitos
- Noções básicas de como usar o Kotlin para desenvolvimento Android
- Conhecimentos básicos sobre o Jetpack Compose. Saiba mais aqui.
O que você vai aprender
- Como criar um projeto do Google Cloud
- Como criar clientes OAuth no Console do Google Cloud
- Como implementar o recurso Fazer login com o Google usando o fluxo da caixa de diálogo inferior
- Como implementar o recurso Fazer login com o Google usando o fluxo de botões
O que é necessário
- Android Studio (faça o download aqui)
- Um computador que atenda aos requisitos do sistema do Android Studio
- Um computador que atenda aos requisitos de sistema do Android Emulator
2. Criar um projeto do Android Studio
Duração: 3:00 - 5:00
Para começar, crie um novo projeto no Android Studio:
- Abrir o Android Studio
- Clique em Novo projeto.
- Selecione Phone and Tablet e Empty Activity
.
- Clique em Próxima.
- Agora é hora de configurar algumas partes do projeto:
- Nome: é o nome do projeto.
- Nome do pacote: será preenchido automaticamente com base no nome do projeto.
- Local de salvamento: por padrão, é a pasta em que o Android Studio salva seus projetos. Você pode mudar isso para onde quiser.
- SDK mínimo: é a versão mais antiga do SDK do Android em que o app foi criado para ser executado. Neste codelab, vamos usar a API 36 (Baklava).
- Clique em Finish
- O Android Studio vai criar o projeto e baixar as dependências necessárias para o aplicativo base. Isso pode levar vários minutos. Para ver isso acontecer, clique no ícone de build:
- Depois disso, o Android Studio vai ficar assim:
3. Configurar seu projeto do Google Cloud
Criar um projeto do Google Cloud
- Acesse o console do Google Cloud.
- Abra ou crie um projeto
- Clique em APIs e serviços
- Acesse a tela de permissão OAuth
- Você precisa preencher os campos em Visão geral para continuar. Clique em Começar para preencher estas informações:
- Nome do app: o nome do app, que deve ser o mesmo usado ao criar o projeto no Android Studio.
- E-mail de suporte ao usuário: mostra a Conta do Google com que você está fazendo login e os Grupos do Google que você gerencia.
- Público-alvo:
- Interno para um app usado apenas na sua organização. Se você não tiver uma organização associada ao projeto do Google Cloud, não será possível selecionar essa opção.
- "External" é o que estamos usando.
- Informações de contato: pode ser qualquer e-mail que você queira usar como ponto de contato para o aplicativo.
- Leia a Política de dados do usuário dos serviços de API do Google.
- Depois de ler e concordar com a Política de Dados do Usuário, clique em Criar
Configurar clientes OAuth
Agora que temos um projeto do Google Cloud configurado, precisamos adicionar um cliente da Web e um cliente Android para fazer chamadas de API ao servidor de back-end OAuth com os IDs de cliente deles.
Para o cliente da Web Android, você vai precisar do seguinte:
- Nome do pacote do seu app (por exemplo, com.example.example)
- A assinatura SHA-1 do seu app
- O que é uma assinatura SHA-1?
- A impressão digital SHA-1 é um hash criptográfico gerado com base na chave de assinatura do app. Ele funciona como um identificador exclusivo do certificado de assinatura do seu app específico. É como uma "assinatura" digital do seu app.
- Por que precisamos da assinatura SHA-1?
- A impressão digital SHA-1 garante que apenas seu app, assinado com sua chave de assinatura específica, possa solicitar tokens de acesso usando seu ID do cliente OAuth 2.0, impedindo que outros apps (mesmo aqueles com o mesmo nome de pacote) acessem os recursos e dados do usuário do seu projeto.
- Pense assim:
- A chave de assinatura do app é como a chave física da "porta" dele. É o que permite o acesso ao funcionamento interno do app.
- A impressão digital SHA-1 é como um ID de cartão-chave exclusivo vinculado à sua chave física. É um código específico que identifica essa chave.
- O ID do cliente OAuth 2.0 é como um código de entrada para um recurso ou serviço específico do Google (por exemplo, o Google Sign-in).
- Ao fornecer a impressão digital SHA-1 durante a configuração do cliente OAuth, você está essencialmente dizendo ao Google: "Somente o cartão com este ID específico (SHA-1) pode abrir este código de acesso (ID do cliente)". Isso garante que apenas seu app possa acessar os serviços do Google vinculados a esse código de entrada".
- O que é uma assinatura SHA-1?
Para o cliente da Web, tudo o que precisamos é do nome que você quer usar para identificar o cliente no console.
Criar um cliente OAuth 2.0 do Android
- Acesse a página Clientes
- Clique em Criar cliente
.
- Selecione Android como o tipo de aplicativo.
- Você precisa especificar o nome do pacote do app
- No Android Studio, vamos precisar pegar a assinatura SHA-1 do nosso app e copiar/colar aqui:
- Acesse o Android Studio e abra o terminal.
- Execute este comando:
Esse comando foi criado para listar os detalhes de uma entrada específica (alias) em um keystore.keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
-list
: essa opção instrui o keytool a listar o conteúdo do keystore.-v
: essa opção ativa a saída detalhada, fornecendo informações mais detalhadas sobre a entrada.-keystore ~/.android/debug.keystore
: especifica o caminho para o arquivo do keystore.-alias androiddebugkey
: especifica o alias (nome da entrada) da chave que você quer inspecionar.-storepass android
: fornece a senha do arquivo do keystore.-keypass android
: fornece a senha da chave privada do alias especificado.
- Copie o valor da assinatura SHA-1:
- Volte para a janela do Google Cloud e cole o valor da assinatura SHA-1:
- Sua tela vai ficar assim. Clique em Criar:
Criar cliente OAuth 2.0 da Web
- Para criar um ID do cliente de aplicativo da Web, repita as etapas 1 e 2 da seção Criar cliente Android e selecione Aplicativo da Web como tipo de aplicativo.
- Dê um nome ao cliente (será o cliente OAuth):
- Clique em Criar
- Copie o ID do cliente da janela pop-up. Você vai precisar dele mais tarde.
Agora que temos todos os clientes OAuth configurados, podemos voltar ao Android Studio e criar nosso app Android de login com o Google.
4. Configurar um dispositivo virtual Android
Para testar rapidamente seu aplicativo sem um dispositivo Android físico, crie um dispositivo virtual Android para criar e executar imediatamente o app no Android Studio. Se quiser testar com um dispositivo Android físico, siga as instruções da documentação para desenvolvedores Android.
Criar um Dispositivo virtual Android
- No Android Studio, abra o Device Manager
- Clique no botão + > Criar dispositivo virtual
- Aqui, você pode adicionar qualquer dispositivo necessário para seu projeto. Para este codelab, selecione Medium Phone e clique em Next
.
- Agora você pode configurar o dispositivo para seu projeto a ele um nome exclusivo, escolhendo a versão do Android que ele vai executar e muito mais. Verifique se a API está definida como API 36 "Baklava"; Android 16 e clique em Concluir
.
- O novo dispositivo vai aparecer no Gerenciador de dispositivos. Para verificar se o dispositivo está funcionando, clique em
ao lado do dispositivo que você acabou de criar
.
- O dispositivo deve estar funcionando agora.
Fazer login no Dispositivo Virtual Android
O dispositivo que você acabou de criar funciona. Agora, para evitar erros ao testar o recurso Fazer login com o Google, faça login no dispositivo com uma Conta do Google.
- Acesse Configurações:
- Clique no centro da tela do dispositivo virtual e deslize para cima.
- Procure e clique no app Configurações.
- Clique em Google em Configurações
.
- Clique em Fazer login e siga as instruções para acessar sua Conta do Google
.
- Agora você fez login no dispositivo
Seu dispositivo Android virtual está pronto para testes.
5. Adicionar dependências
Duração: 5:00
Para fazer chamadas à API OAuth, primeiro precisamos integrar as bibliotecas necessárias que permitem fazer solicitações de autenticação e usar IDs do Google para fazer essas solicitações:
- libs.googleid
- libs.play.services.auth
- Acesse File > Project Structure:
- Em seguida, acesse Dependencies > app > '+' > Library Dependency
- Agora precisamos adicionar nossas bibliotecas:
- Na caixa de diálogo de pesquisa, digite googleid e clique em Pesquisar.
- Selecione a única entrada e a versão mais recente disponível (no momento deste codelab, é a 1.1.1).
- Clique em OK
.
- Repita as etapas de 1 a 3, mas pesquise "play-services-auth" e selecione a linha com "com.google.android.gms" como o ID do grupo e "play-services-auth" como o Nome do artefato
- Clique em OK
.
6. Fluxo da página inferior
O fluxo da página inferior usa a API Credential Manager para que os usuários possam fazer login no seu app usando as Contas do Google no Android de maneira simplificada. Ele foi projetado para ser rápido e conveniente, especialmente para usuários recorrentes. Esse fluxo precisa ser acionado na inicialização do app.
Criar a solicitação de login
- Para começar, remova as funções
Greeting()
eGreetingPreview()
deMainActivity.kt
. Não vamos precisar delas. - Agora precisamos garantir que os pacotes necessários sejam importados para este projeto. Adicione as seguintes instruções
import
após as atuais, começando na linha 3::import android.content.ContentValues.TAG import android.content.Context import android.credentials.GetCredentialException import android.os.Build import android.util.Log import android.widget.Toast import androidx.annotation.RequiresApi import androidx.compose.foundation.clickable import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.credentials.CredentialManager import androidx.credentials.exceptions.GetCredentialCancellationException import androidx.credentials.exceptions.GetCredentialCustomException import androidx.credentials.exceptions.NoCredentialException import androidx.credentials.GetCredentialRequest import com.google.android.libraries.identity.googleid.GetGoogleIdOption import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException import java.security.SecureRandom import java.util.Base64 import kotlinx.coroutines.CoroutineScope import androidx.compose.runtime.LaunchedEffect import kotlinx.coroutines.delay import kotlinx.coroutines.launch
- Em seguida, precisamos criar nossa função para criar a solicitação do Bottom Sheet. Cole esse código abaixo da classe MainActivity.
//This line is not needed for the project to build, but you will see errors if it is not present.
//This code will not work on Android versions < UpsideDownCake
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Composable
fun BottomSheet(webClientId: String) {
val context = LocalContext.current
// LaunchedEffect is used to run a suspend function when the composable is first launched.
LaunchedEffect(Unit) {
// Create a Google ID option with filtering by authorized accounts enabled.
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(true)
.setServerClientId(webClientId)
.setNonce(generateSecureRandomNonce())
.build()
// Create a credential request with the Google ID option.
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build()
// Attempt to sign in with the created request using an authorized account
val e = signIn(request, context)
// If the sign-in fails with NoCredentialException, there are no authorized accounts.
// In this case, we attempt to sign in again with filtering disabled.
if (e is NoCredentialException) {
val googleIdOptionFalse: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(false)
.setServerClientId(webClientId)
.setNonce(generateSecureRandomNonce())
.build()
val requestFalse: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOptionFalse)
.build()
//We will build out this function in a moment
signIn(requestFalse, context)
}
}
}
//This function is used to generate a secure nonce to pass in with our request
fun generateSecureRandomNonce(byteLength: Int = 32): String {
val randomBytes = ByteArray(byteLength)
SecureRandom.getInstanceStrong().nextBytes(randomBytes)
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
}
Vamos detalhar o que esse código faz:
fun BottomSheet(webClientId: String) {...}
: cria uma função chamada BottomSheet que usa um argumento de string chamado webClientid.
val context = LocalContext.current
: recupera o contexto atual do Android. Isso é necessário para várias operações, incluindo o lançamento de componentes da interface.LaunchedEffect(Unit) { ... }
:LaunchedEffect
é um elemento combinável do Jetpack Compose que permite executar uma função de suspensão (uma função que pode pausar e retomar a execução) no ciclo de vida do elemento combinável. "Unit" como chave significa que esse efeito só será executado uma vez quando o elemento combinável for iniciado.val googleIdOption: GetGoogleIdOption = ...
: cria um objetoGetGoogleIdOption
. Esse objeto configura o tipo de credencial que está sendo solicitada ao Google..Builder()
: um padrão de builder é usado para configurar as opções..setFilterByAuthorizedAccounts(true)
: especifica se o usuário pode selecionar todas as Contas do Google ou apenas as que já autorizaram o app. Nesse caso, está definido como "true", o que significa que a solicitação será feita usando credenciais que o usuário autorizou anteriormente para uso com esse app, se houver alguma disponível..setServerClientId(webClientId)
: define o ID do cliente do servidor, que é um identificador exclusivo para o back-end do app. Isso é necessário para receber um token de ID..setNonce(generateSecureRandomNonce())
: define um valor de uso único, um valor aleatório, para evitar ataques de repetição e garantir que o token de ID esteja associado à solicitação específica..build()
: cria o objetoGetGoogleIdOption
com a configuração especificada.
val request: GetCredentialRequest = ...
: cria um objetoGetCredentialRequest
. Esse objeto encapsula toda a solicitação de credencial..Builder()
: inicia o padrão de builder para configurar a solicitação..addCredentialOption(googleIdOption)
: adiciona o googleIdOption à solicitação, especificando que queremos solicitar um token de ID do Google..build()
: cria o objetoGetCredentialRequest
.
val e = signIn(request, context)
: tenta fazer login do usuário com a solicitação criada e o contexto atual. O resultado da função signIn é armazenado em "e". Essa variável vai conter o resultado bem-sucedido ou uma exceção.if (e is NoCredentialException) { ... }
: é uma verificação condicional. Se a função signIn falhar com uma NoCredentialException, significa que não há contas autorizadas disponíveis.val googleIdOptionFalse: GetGoogleIdOption = ...
: se osignIn
anterior falhar, esta parte vai criar um novoGetGoogleIdOption
..setFilterByAuthorizedAccounts(false)
: essa é a diferença crucial da primeira opção. Ele desativa a filtragem de contas autorizadas, ou seja, qualquer Conta do Google no dispositivo pode ser usada para fazer login.val requestFalse: GetCredentialRequest = ...
: um novoGetCredentialRequest
é criado com ogoogleIdOptionFalse
.signIn(requestFalse, context)
: tenta fazer login do usuário com a nova solicitação, que permite o uso de qualquer conta.
Em essência, esse código prepara uma solicitação para a API Credential Manager para recuperar um token de ID do Google para o usuário, usando as configurações fornecidas. O GetCredentialRequest pode ser usado para iniciar a interface do gerenciador de credenciais, em que o usuário pode selecionar a Conta do Google e conceder as permissões necessárias.
fun generateSecureRandomNonce(byteLength: Int = 32): String
: define uma função chamada generateSecureRandomNonce
. Ele aceita um argumento inteiro byteLength (com um valor padrão de 32) que especifica o comprimento desejado do nonce em bytes. Ela retorna uma string, que será a representação codificada em Base64 dos bytes aleatórios.
val randomBytes = ByteArray(byteLength)
: cria uma matriz de bytes do byteLength especificado para armazenar os bytes aleatórios.SecureRandom.getInstanceStrong().nextBytes(randomBytes)
:SecureRandom.getInstanceStrong()
: isso gera um gerador de números aleatórios criptograficamente forte. Isso é crucial para a segurança, já que garante que os números gerados sejam realmente aleatórios e não previsíveis. Ele usa a fonte de entropia mais forte disponível no sistema..nextBytes(randomBytes)
: preenche a matriz randomBytes com bytes aleatórios gerados pela instância SecureRandom.
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
:Base64.getUrlEncoder()
: recebe um codificador Base64 que usa um alfabeto seguro para URL (usando - e _ em vez de + e /). Isso é importante porque garante que a string resultante possa ser usada com segurança em URLs sem precisar de mais codificação..withoutPadding()
: isso remove todos os caracteres de padding da string codificada em Base64. Isso geralmente é desejável para tornar o nonce um pouco mais curto e compacto..encodeToString(randomBytes)
: codifica os randomBytes em uma string Base64 e a retorna.
Em resumo, essa função gera um nonce aleatório criptograficamente forte de um comprimento especificado, o codifica usando Base64 seguro para URL e retorna a string resultante. Essa é uma prática padrão para gerar nonces seguros para uso em contextos sensíveis à segurança.
Fazer a solicitação de login
Agora que podemos criar nossa solicitação de login, podemos usar o Gerenciador de credenciais para fazer login. Para isso, precisamos criar uma função que processe a transmissão de solicitações de login usando o Credential Manager e que lide com exceções comuns que podem ocorrer.
Para fazer isso, cole a função abaixo da função BottomSheet()
.
//This code will not work on Android versions < UPSIDE_DOWN_CAKE when GetCredentialException is
//is thrown.
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
suspend fun signIn(request: GetCredentialRequest, context: Context): Exception? {
val credentialManager = CredentialManager.create(context)
val failureMessage = "Sign in failed!"
var e: Exception? = null
//using delay() here helps prevent NoCredentialException when the BottomSheet Flow is triggered
//on the initial running of our app
delay(250)
try {
// The getCredential is called to request a credential from Credential Manager.
val result = credentialManager.getCredential(
request = request,
context = context,
)
Log.i(TAG, result.toString())
Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
Log.i(TAG, "(☞゚ヮ゚)☞ Sign in Successful! ☜(゚ヮ゚☜)")
} catch (e: GetCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Failure getting credentials", e)
} catch (e: GoogleIdTokenParsingException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with parsing received GoogleIdToken", e)
} catch (e: NoCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": No credentials found", e)
return e
} catch (e: GetCredentialCustomException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with custom credential request", e)
} catch (e: GetCredentialCancellationException) {
Toast.makeText(context, ": Sign-in cancelled", Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Sign-in was cancelled", e)
}
return e
}
Agora, vamos detalhar o que o código está fazendo aqui:
suspend fun signIn(request: GetCredentialRequest, context: Context): Exception?
: isso define uma função de suspensão chamada signIn. Isso significa que ele pode ser pausado e retomado sem bloquear a linha de execução principal.Ele retorna um Exception?
, que será nulo se o login for bem-sucedido ou a exceção específica se o login falhar.
Ele usa dois parâmetros:
request
: um objetoGetCredentialRequest
, que contém a configuração do tipo de credencial a ser recuperada (por exemplo, ID do Google).context
: o contexto do Android necessário para interagir com o sistema.
Para o corpo da função:
val credentialManager = CredentialManager.create(context)
: cria uma instância do CredentialManager, que é a principal interface para interagir com a API Credential Manager. É assim que o app vai iniciar o fluxo de login.val failureMessage = "Sign in failed!"
: define uma string (failureMessage) a ser exibida em um toast quando o login falha.var e: Exception? = null
: essa linha inicializa uma variável "e" para armazenar qualquer exceção que possa ocorrer durante o processo, começando com "null".delay(250)
: introduz um atraso de 250 milissegundos. Essa é uma solução alternativa para um possível problema em que "NoCredentialException" pode ser gerado imediatamente quando o app é iniciado, principalmente ao usar um fluxo BottomSheet. Isso dá tempo para o sistema inicializar o gerenciador de credenciais.try { ... } catch (e: Exception) { ... }
:um bloco try-catch é usado para um tratamento de erros robusto. Isso garante que, se ocorrer algum erro durante o processo de login, o app não falhe e possa processar a exceção corretamente.val result = credentialManager.getCredential(request = request, context = context)
: é aqui que a chamada real para a API Credential Manager acontece e inicia o processo de recuperação de credenciais. Ele usa a solicitação e o contexto como entrada e apresenta uma interface para o usuário selecionar uma credencial. Se a operação for bem-sucedida, ela vai retornar um resultado com a credencial selecionada. O resultado dessa operação,GetCredentialResponse
, é armazenado na variávelresult
.Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
:mostra uma breve mensagem de aviso indicando que o login foi bem-sucedido.Log.i(TAG, "Sign in Successful!")
: registra uma mensagem divertida e bem-sucedida no logcat.catch (e: GetCredentialException)
: processa exceções do tipoGetCredentialException
. Essa é uma classe mãe para várias exceções específicas que podem ocorrer durante o processo de busca de credenciais.catch (e: GoogleIdTokenParsingException)
: processa exceções que ocorrem quando há um erro ao analisar o ID do token do Google.catch (e: NoCredentialException)
: processa oNoCredentialException
, que é gerado quando não há credenciais disponíveis para o usuário (por exemplo, ele não salvou nenhuma ou não tem uma Conta do Google).- É importante lembrar que essa função retorna a exceção armazenada em
e
,NoCredentialException
, permitindo que o autor da chamada processe o caso específico se nenhuma credencial estiver disponível.
- É importante lembrar que essa função retorna a exceção armazenada em
catch (e: GetCredentialCustomException)
: processa exceções personalizadas que podem ser geradas pelo provedor de credenciais.catch (e: GetCredentialCancellationException)
: processa oGetCredentialCancellationException
, que é gerado quando o usuário cancela o processo de login.Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
: mostra uma mensagem de toast indicando que o login falhou usando o "failureMessage".Log.e(TAG, "", e)
: registra a exceção no logcat do Android usando Log.e, que é usado para erros. Isso vai incluir o rastreamento de pilha da exceção para ajudar na depuração. Ele também inclui o emoticon de raiva para diversão.
return e
: a função retorna a exceção se alguma for capturada ou nula se o login for bem-sucedido.
Em resumo, esse código oferece uma maneira de processar o login do usuário usando a API Credential Manager, gerencia a operação assíncrona, processa possíveis erros e fornece feedback ao usuário por meio de toasts e registros, além de adicionar um toque de humor ao tratamento de erros.
Implementar o fluxo da página inferior no app
Agora podemos configurar uma chamada para acionar o fluxo BottomSheet na nossa classe MainActivity
usando o código a seguir e o ID do cliente do aplicativo da Web que copiamos do console do Google Cloud anteriormente:
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//replace with your own web client ID from Google Cloud Console
val webClientId = "YOUR_CLIENT_ID_HERE"
setContent {
//ExampleTheme - this is derived from the name of the project not any added library
//e.g. if this project was named "Testing" it would be generated as TestingTheme
ExampleTheme {
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background,
) {
//This will trigger on launch
BottomSheet(webClientId)
}
}
}
}
}
Agora podemos salvar nosso projeto (Arquivo > Salvar) e executá-lo:
- Pressione o botão de execução:
- Depois que o app for executado no emulador, a BottomSheet de login vai aparecer. Clique em Continuar para testar o login
- Você vai ver uma mensagem de aviso mostrando que o login foi feito com sucesso.
7. Fluxo de botões
O fluxo de botões para "Fazer login com o Google" facilita o cadastro ou login dos usuários no seu app Android usando a Conta do Google. Ele será acionado se o usuário dispensar a folha de baixo ou preferir usar explicitamente a Conta do Google para fazer login ou inscrição. Para os desenvolvedores, isso significa uma integração mais tranquila e menos atrito durante a inscrição.
Embora isso possa ser feito com um botão do Jetpack Compose pronto para uso, vamos usar um ícone de marca pré-aprovado da página Diretrizes de branding do Fazer login com o Google.
Adicionar o ícone da marca ao projeto
- Faça o download do ZIP de ícones de marca pré-aprovados aqui
- Descompacte o arquivo signin-assest.zip dos seus downloads. Isso varia de acordo com o sistema operacional do computador. Agora você pode abrir a pasta "signin-assets" e conferir os ícones disponíveis. Neste codelab, vamos usar
signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png
. - Copie o arquivo
- Cole no projeto do Android Studio em res > drawable. Para isso, clique com o botão direito do mouse na pasta drawable e clique em Colar. Talvez seja necessário expandir a pasta res para que ela apareça.
- Uma caixa de diálogo vai aparecer pedindo que você renomeie o arquivo e confirme o diretório em que ele será adicionado. Renomeie o recurso como siwg_button.png e clique em OK
Código do fluxo de botões
Esse código usa a mesma função signIn()
usada para BottomSheet()
, mas usa GetSignInWithGoogleOption
em vez de GetGoogleIdOption
, já que esse fluxo não aproveita as credenciais e chaves de acesso armazenadas no dispositivo para mostrar opções de login. Este é o código que você pode colar abaixo da função BottomSheet()
:
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Composable
fun ButtonUI(webClientId: String) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val onClick: () -> Unit = {
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption
.Builder(serverClientId = webClientId)
.setNonce(generateSecureRandomNonce())
.build()
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(signInWithGoogleOption)
.build()
coroutineScope.launch {
signIn(request, context)
}
}
Image(
painter = painterResource(id = R.drawable.siwg_button),
contentDescription = "",
modifier = Modifier
.fillMaxSize()
.clickable(enabled = true, onClick = onClick)
)
}
Para entender o que o código está fazendo:
fun ButtonUI(webClientId: String)
: declara uma função chamada ButtonUI
que aceita um webClientId
(o ID do cliente do seu projeto do Google Cloud) como argumento.
val context = LocalContext.current
: recupera o contexto atual do Android. Isso é necessário para várias operações, incluindo o lançamento de componentes da interface.
val coroutineScope = rememberCoroutineScope()
: cria um escopo de corrotina. Isso é usado para gerenciar tarefas assíncronas, permitindo que o código seja executado sem bloquear a linha de execução principal. O rememberCoroutineScope
() é uma função combinável do Jetpack Compose que fornece um escopo vinculado ao ciclo de vida do elemento combinável.
val onClick: () -> Unit = { ... }
: isso cria uma função lambda que será executada quando o botão for clicado. A função lambda vai:
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(serverClientId = webClientId).setNonce(generateSecureRandomNonce()).build()
: esta parte cria um objetoGetSignInWithGoogleOption
. Esse objeto é usado para especificar os parâmetros do processo "Fazer login com o Google". Ele exige owebClientId
e um nonce (uma string aleatória usada para segurança).val request: GetCredentialRequest = GetCredentialRequest.Builder().addCredentialOption(signInWithGoogleOption).build()
: cria um objetoGetCredentialRequest
. Essa solicitação será usada para receber a credencial do usuário usando o Gerenciador de credenciais. OGetCredentialRequest
adiciona oGetSignInWithGoogleOption
criado anteriormente como uma opção para solicitar uma credencial de "Fazer login com o Google".
coroutineScope.launch { ... }
: umCoroutineScope
para gerenciar operações assíncronas (usando corrotinas).signIn(request, context)
: chama a funçãosignIn
() definida anteriormente.
Image(...)
: isso renderiza uma imagem usando o painterResource
que carrega a imagem R.drawable.siwg_button
Modifier.fillMaxSize().clickable(enabled = true, onClick = onClick)
:fillMaxSize()
: faz a imagem preencher o espaço disponível.clickable(enabled = true, onClick = onClick)
: torna a imagem clicável e, quando clicada, executa a função lambda onClick definida anteriormente.
Em resumo, esse código configura um botão "Fazer login com o Google" em uma interface do Jetpack Compose. Quando o botão é clicado, ele prepara uma solicitação de credencial para iniciar o Gerenciador de credenciais e permitir que o usuário faça login com a Conta do Google.
Agora, precisamos atualizar a classe MainActivity para executar nossa função ButtonUI()
:
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//replace with your own web client ID from Google Cloud Console
val webClientId = "YOUR_CLIENT_ID_HERE"
setContent {
//ExampleTheme - this is derived from the name of the project not any added library
//e.g. if this project was named "Testing" it would be generated as TestingTheme
ExampleTheme {
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background,
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
//This will trigger on launch
BottomSheet(webClientId)
//This requires the user to press the button
ButtonUI(webClientId)
}
}
}
}
}
}
Agora podemos salvar nosso projeto (Arquivo > Salvar) e executá-lo:
- Pressione o botão de execução:
- Quando o app for executado no emulador, o BottomSheet vai aparecer. Clique fora dela para fechar.
- Agora, o botão que criamos vai aparecer no app. Clique nele para ver a caixa de diálogo de login
- Clique na sua conta para fazer login.
8. Conclusão
Você concluiu este codelab. Para mais informações ou ajuda sobre o recurso Fazer login com o Google no Android, consulte a seção de perguntas frequentes abaixo:
Perguntas frequentes
- Stack Overflow
- Guia de solução de problemas do Gerenciador de credenciais do Android
- Perguntas frequentes sobre o Gerenciador de credenciais do Android
- Central de Ajuda da verificação de apps com OAuth
Código completo de MainActivity.kt
Confira o código completo de MainActivity.kt para referência:
package com.example.example
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.example.example.ui.theme.ExampleTheme
import android.content.ContentValues.TAG
import android.content.Context
import android.credentials.GetCredentialException
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.credentials.CredentialManager
import androidx.credentials.exceptions.GetCredentialCancellationException
import androidx.credentials.exceptions.GetCredentialCustomException
import androidx.credentials.exceptions.NoCredentialException
import androidx.credentials.GetCredentialRequest
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
import java.security.SecureRandom
import java.util.Base64
import kotlinx.coroutines.CoroutineScope
import androidx.compose.runtime.LaunchedEffect
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//replace with your own web client ID from Google Cloud Console
val webClientId = "YOUR_CLIENT_ID_HERE"
setContent {
//ExampleTheme - this is derived from the name of the project not any added library
//e.g. if this project was named "Testing" it would be generated as TestingTheme
ExampleTheme {
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background,
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
//This will trigger on launch
BottomSheet(webClientId)
//This requires the user to press the button
ButtonUI(webClientId)
}
}
}
}
}
}
//This line is not needed for the project to build, but you will see errors if it is not present.
//This code will not work on Android versions < UpsideDownCake
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Composable
fun BottomSheet(webClientId: String) {
val context = LocalContext.current
// LaunchedEffect is used to run a suspend function when the composable is first launched.
LaunchedEffect(Unit) {
// Create a Google ID option with filtering by authorized accounts enabled.
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(true)
.setServerClientId(webClientId)
.setNonce(generateSecureRandomNonce())
.build()
// Create a credential request with the Google ID option.
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build()
// Attempt to sign in with the created request using an authorized account
val e = signIn(request, context)
// If the sign-in fails with NoCredentialException, there are no authorized accounts.
// In this case, we attempt to sign in again with filtering disabled.
if (e is NoCredentialException) {
val googleIdOptionFalse: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(false)
.setServerClientId(webClientId)
.setNonce(generateSecureRandomNonce())
.build()
val requestFalse: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOptionFalse)
.build()
signIn(requestFalse, context)
}
}
}
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Composable
fun ButtonUI(webClientId: String) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val onClick: () -> Unit = {
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption
.Builder(serverClientId = webClientId)
.setNonce(generateSecureRandomNonce())
.build()
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(signInWithGoogleOption)
.build()
signIn(coroutineScope, request, context)
}
Image(
painter = painterResource(id = R.drawable.siwg_button),
contentDescription = "",
modifier = Modifier
.fillMaxSize()
.clickable(enabled = true, onClick = onClick)
)
}
fun generateSecureRandomNonce(byteLength: Int = 32): String {
val randomBytes = ByteArray(byteLength)
SecureRandom.getInstanceStrong().nextBytes(randomBytes)
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
}
//This code will not work on Android versions < UPSIDE_DOWN_CAKE when GetCredentialException is
//is thrown.
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
suspend fun signIn(request: GetCredentialRequest, context: Context): Exception? {
val credentialManager = CredentialManager.create(context)
val failureMessage = "Sign in failed!"
var e: Exception? = null
//using delay() here helps prevent NoCredentialException when the BottomSheet Flow is triggered
//on the initial running of our app
delay(250)
try {
// The getCredential is called to request a credential from Credential Manager.
val result = credentialManager.getCredential(
request = request,
context = context,
)
Log.i(TAG, result.toString())
Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
Log.i(TAG, "(☞゚ヮ゚)☞ Sign in Successful! ☜(゚ヮ゚☜)")
} catch (e: GetCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Failure getting credentials", e)
} catch (e: GoogleIdTokenParsingException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with parsing received GoogleIdToken", e)
} catch (e: NoCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": No credentials found", e)
return e
} catch (e: GetCredentialCustomException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with custom credential request", e)
} catch (e: GetCredentialCancellationException) {
Toast.makeText(context, ": Sign-in cancelled", Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Sign-in was cancelled", e)
}
return e
}