Google Регистрация в Android на kotlin + jetpack compose
Привет всем! Это моя первая статья на Habr. Сегодня мы поговорим о том, как написать регистрацию через Google. Все мы видели, что во многих крупных приложениях есть возможность зарегистрироваться через Google аккаунт, и многим программистам хотелось бы реализовать это в своём приложении. Это несложно! В этой статье мы рассмотрим весь код и пошаговую инструкцию на Habr.


Сначала нам нужно в Android Studio подключить наш проект к Firebase.
В верхнем меню находим Tools --> Firebase --> Authentication --> Authentication using Google.


Шаг 1: Не забываем включить регистрацию в Firebase.

2: Добавим отпечатки пальцев нашего приложения в Firebase. Нажимаем на шестирёнку справа от слова Project Overview -> Project settings и листаем вниз.

Вы спросите, где их получить? Это делается просто.

Не забываем, если у вас не открыт Gradle (иконка слоника), то нажмите на зелёный терминал >_ и введите туда команду:
gradle app:signingReport
Внизу в Run терминале появится список с вашими отпечатками пальцев. Нам нужны хэши SHA1 и SHA-256 в формате:
в формате 32:2N:G7:44:GD:FF:54:44:BB:32:67:10:8C:09:EH:8E:D#:A6:44:18
Добавляем их в раздел с отпечатками пальцев в нашем проекте Firebase.
После этого, в том же месте, где мы добавляли отпечатки пальцев, нам нужно скачать файл google-services.json
для нашего приложения. Скачиваем его и добавляем в корень нашего проекта. как на фото

app
добавляем наш google-services.json
.Также не забудьте добавить конфигурацию Web SDK.


Как только скопировали его, не забудьте в ресурсных файлах скопировать и вписать в strings.xml
в таком формате: это понадобиться нам для создания запроса
<string name="web_client_id"> вашь ключ </string>
Открываем наш Gradle файл и добавляем туда зависимости, такие как Coil для отрисовки изображений и для работы с API Google.
implementation("io.coil-kt:coil-compose:2.4.0")
implementation(platform("com.google.firebase:firebase-bom:33.1.1")) // Firebase Bill of Materials (BOM)
implementation("com.google.firebase:firebase-auth-ktx") // Firebase Authentication (Kotlin)
implementation("com.google.android.gms:play-services-auth:21.2.0") // Google Play Services Auth
implementation("androidx.compose.material3:material3:1.2.1") // Замените на актуальную версию
Пишем код

Если вы сделали всё правильно, теперь осталось только написать код.
В файл SignInResult.kt
впишите этот код - это data class для наших данных пользователя:
data class SignInResult(
val data: UserData?,
val errorMessage: String?
)
data class UserData(
val userId: String,
val username: String?,
val profilePictureUrl: String?
)
В фаел singinState впишите это код
data class SignInState(
val isSignInSuccessful: Boolean = false,
val signInError: String? = null
)
Поскольку в проекте мы будем отображать всё через фрагменты, нам нужно написать код для нашего фрагмента следующим образом, чтобы отобразить профиль пользователя. Код пишем в файл ProfileScreen.kt
:
@Composable
fun ProfileScreen(
userData: UserData?,
onSignOut: () -> Unit
) {
Column(
modifier = Modifier.height(600.dp).fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
if(userData?.profilePictureUrl != null) {
AsyncImage(
model = userData.profilePictureUrl,
contentDescription = "Profile picture",
modifier = Modifier
.size(150.dp)
.clip(CircleShape),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.height(16.dp))
}
if(userData?.username != null) {
Text(
text = userData.username,
textAlign = TextAlign.Center,
fontSize = 36.sp,
fontWeight = FontWeight.SemiBold
)
Spacer(modifier = Modifier.height(16.dp))
}
Button(colors = ButtonDefaults.buttonColors(Color(0xFF03A9F4)),onClick = onSignOut, modifier = Modifier.clip(RoundedCornerShape(90.dp)), shape = RoundedCornerShape(20.dp)) {
Text(text = "Sign out")
}
}
}
Дальше мы пишем код, представляющий собой клиент для аутентификации пользователя через Google с использованием Firebase Authentication и Google One Tap Sign-In. В файл GoogleAuthUiClient.kt
вставьте следующий код с комментариями, чтобы все могли понять, что происходит:
// Определение класса для управления аутентификацией через Google
class GoogleAuthUiClient(
private val context: Context, // Контекст приложения
private val oneTapClient: SignInClient // Клиент для One Tap Sign-In
) {
// Инициализация экземпляра Firebase Authentication
private val auth = Firebase.auth
// Функция для начала процесса входа в систему
suspend fun signIn(): IntentSender? {
// Попытка начать процесс входа через One Tap
val result = try {
oneTapClient.beginSignIn(
buildSignInRequest() // Построение запроса на вход
).await()
} catch(e: Exception) {
// Обработка ошибок, если вход не удался
e.printStackTrace()
Log.e("GoogleSignIn", "Sign in failed", e)
if(e is CancellationException) throw e
null
}
// Возвращение IntentSender, если вход успешен
return result?.pendingIntent?.intentSender
}
// Функция для завершения процесса входа с Intent
suspend fun signInWithIntent(intent: Intent): SignInResult {
// Получение учетных данных из Intent
val credential = oneTapClient.getSignInCredentialFromIntent(intent)
val googleIdToken = credential.googleIdToken
val googleCredentials = GoogleAuthProvider.getCredential(googleIdToken, null)
// Попытка войти в Firebase с полученными учетными данными
return try {
val user = auth.signInWithCredential(googleCredentials).await().user
Log.d("GoogleSignIn", "Sign in successful: ${user?.displayName}")
SignInResult(
data = user?.run {
UserData(
userId = uid,
username = displayName,
profilePictureUrl = photoUrl?.toString()
)
},
errorMessage = null
)
} catch(e: Exception) {
// Обработка ошибок при входе
Log.e("GoogleSignIn", "Sign in failed", e)
e.printStackTrace()
if(e is CancellationException) throw e
SignInResult(
data = null,
errorMessage = e.message
)
}
}
// Функция для выхода из системы
suspend fun signOut() {
try {
// Выход из One Tap и Firebase
oneTapClient.signOut().await()
auth.signOut()
Log.d("GoogleSignIn", "Sign out successful")
} catch(e: Exception) {
// Обработка ошибок при выходе
Log.e("GoogleSignIn", "Sign out failed", e)
e.printStackTrace()
if(e is CancellationException) throw e
}
}
// Функция для получения данных о текущем авторизованном пользователе
fun getSignedInUser(): UserData? = auth.currentUser?.run {
UserData(
userId = uid,
username = displayName,
profilePictureUrl = photoUrl?.toString()
)
}
// Построение запроса на вход
private fun buildSignInRequest(): BeginSignInRequest {
return BeginSignInRequest.Builder()
.setGoogleIdTokenRequestOptions(
BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
.setSupported(true)
.setFilterByAuthorizedAccounts(false)
.setServerClientId(context.getString(R.string.web_client_id))
.build()
)
.setAutoSelectEnabled(true)
.build()
}
}
И, конечно же, основной код: если вам нужна фотография с названием googl.png
, и при успешной регистрации пользователя он попадает в активити Main_menu
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.ilya.codewithfriends.presentation.profile.ProfileScreen
import com.ilya.codewithfriends.presentation.sign_in.GoogleAuthUiClient
import com.ilya.codewithfriends.presentation.sign_in.SignInState
import com.ilya.codewithfriends.presentation.sign_in.SignInViewModel
import com.ilya.codewithfriends.presentation.sign_in.UserData
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.compose.ComposeGoogleSignInCleanArchitectureTheme
import com.google.android.gms.auth.api.identity.Identity
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.api.ApiException
import com.google.firebase.auth.AuthResult
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseAuthUserCollisionException
import com.google.firebase.auth.GoogleAuthProvider
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import com.ilya.MeetingMap.Mine_menu.Main_menuclass MainActivity : ComponentActivity() {
у вас свои бииотеки должны быть
private lateinit var auth: FirebaseAuth
var leftop by mutableStateOf(true)
var username by mutableStateOf("")
var password by mutableStateOf("")
var cloth by mutableStateOf(true)
private val googleAuthUiClient by lazy {
GoogleAuthUiClient(
context = applicationContext,
oneTapClient = Identity.getSignInClient(applicationContext)
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
toshear(userData = googleAuthUiClient.getSignedInUser())
ComposeGoogleSignInCleanArchitectureTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "sign_in") {
composable("sign_in") {
val viewModel = viewModel<SignInViewModel>()
val state by viewModel.state.collectAsStateWithLifecycle()
LaunchedEffect(key1 = Unit) {
if (googleAuthUiClient.getSignedInUser() != null) {
navController.navigate("profile")
}
}
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartIntentSenderForResult(),
onResult = { result ->
if (result.resultCode == RESULT_OK) {
lifecycleScope.launch {
val signInResult = googleAuthUiClient.signInWithIntent(
intent = result.data ?: return@launch
)
viewModel.onSignInResult(signInResult)
}
}
}
)
LaunchedEffect(key1 = state.isSignInSuccessful) {
if (state.isSignInSuccessful) {
Toast.makeText(
applicationContext,
"Регистрация прошла успешно",
Toast.LENGTH_LONG
).show()
navController.navigate("profile")
viewModel.resetState()
leftop = !leftop
}
}
SignInScreen(
state = state,
onSignInClick = {
lifecycleScope.launch {
val signInIntentSender = googleAuthUiClient.signIn()
launcher.launch(
IntentSenderRequest.Builder(
signInIntentSender ?: return@launch
).build()
)
}
},
navController
)
}
composable("profile") {
Column(modifier = Modifier.fillMaxSize()) {
ProfileScreen(
userData = googleAuthUiClient.getSignedInUser(),
onSignOut = {
lifecycleScope.launch {
googleAuthUiClient.signOut()
Toast.makeText(
applicationContext,
"Goodbye",
Toast.LENGTH_LONG
).show()
navController.popBackStack()
}
}
)
backtomenu()
}
}
}
}
}
}
}
@Composable
fun SignInScreen(
state: SignInState,
onSignInClick: () -> Unit,
navController: NavController
) {
var unvisible by remember {
mutableStateOf(false)
}
var user by remember { mutableStateOf(Firebase.auth.currentUser) }
val launcher = rememberFirebaseAuthLauncher(
onAuthComplete = { result ->
user = result.user
// navController.navigate("profile")
},
onAuthError = {
user = null
}
)
val token = stringResource(id = R.string.web_client_id)
val context = LocalContext.current
val scope = rememberCoroutineScope()
val serverSetting = remember { mutableStateOf(false) }
Box(
modifier = Modifier
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
if (!leftop) {
toshear(userData = googleAuthUiClient.getSignedInUser())
Box(
modifier = Modifier
.height(400.dp)
.align(Alignment.Center)
.padding(top = 100.dp)
) {
LoadingCircle()
}
}
if (user == null) {
if (!unvisible) {
Column(
modifier = Modifier
.wrapContentSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
IconButton(
onClick = {
Log.d("GoogleSignIn", "Attempting to sign in")
val gso =
GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(token)
.requestEmail()
.build()
val googleSignInClient = GoogleSignIn.getClient(context, gso)
launcher.launch(googleSignInClient.signInIntent)
leftop = !leftop
unvisible = !unvisible
Log.d("GoogleSignIn", "Sign in intent launched")
}
) {
Image(
painter = painterResource(id = R.drawable.google),
contentDescription = "Nothing",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(50.dp)
.clip(CircleShape)
)
}
Spacer(modifier = Modifier.height(10.dp))
Text(stringResource(id = R.string.login))
Spacer(modifier = Modifier.height(10.dp))
Spacer(modifier = Modifier.height(10.dp))
Box(
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
)
}
}
}
}
}
@Composable
fun toshear(userData: UserData?) {
if (userData?.username != null) {
val intent = Intent(this@MainActivity, Main_menu::class.java)
startActivity(intent)
}
}
@Composable
fun backtomenu() {
Box(
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
contentAlignment = Alignment.BottomEnd // Размещаем Box внизу
) {
Button(
modifier = Modifier
.fillMaxWidth()
.padding(start = 70.dp, end = 70.dp)
.height(50.dp)
.align(Alignment.Center),
colors = ButtonDefaults.buttonColors(Color(0xFF4CAF50)),
shape = RoundedCornerShape(20.dp),
onClick = {
val intent = Intent(this@MainActivity, Main_menu::class.java)
startActivity(intent)
finish()
}
)
{
Text(stringResource(id = R.string.back))
}
}
}
@Preview(showBackground = true)
@Composable
fun LoadingCircle() {
Box(
modifier = Modifier
.height(100.dp)
.wrapContentSize(Alignment.TopCenter)
) {
val rotation = rememberInfiniteTransition().animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 1000, easing = LinearEasing),
repeatMode = RepeatMode.Restart
)
)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.size(100.dp)
) {
CircularProgressIndicator(
modifier = Modifier
.size(90.dp)
//.rotate(rotation.value)
)
}
}
}
@Composable
fun rememberFirebaseAuthLauncher(
onAuthComplete: (AuthResult) -> Unit,
onAuthError: (ApiException) -> Unit
): ManagedActivityResultLauncher<Intent, ActivityResult> {
val scope = rememberCoroutineScope()
return rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
try {
val account = task.getResult(ApiException::class.java)!!
Log.d("GoogleAuth", "account $account")
val credential = GoogleAuthProvider.getCredential(account.idToken!!, null)
scope.launch {
val authResult = Firebase.auth.signInWithCredential(credential).await()
onAuthComplete(authResult)
}
} catch (e: ApiException) {
Log.d("GoogleAuth", e.toString())
onAuthError(e)
}
}
}
private fun registerUser(
context: Context,
auth: FirebaseAuth,
email: String,
password: String,
onResult: (Boolean) -> Unit
) {
auth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
// Registration successful
val user = auth.currentUser
Log.d("Registration", "User registered successfully")
onResult(true) // Пользователь успешно зарегистрирован
} else {
// Registration failed
val exception = task.exception
if (exception is FirebaseAuthUserCollisionException) {
// Пользователь уже существует, попытаемся войти
signInUser(context, auth, email, password, onResult)
} else {
// Другая ошибка, обработаем ее
val message = exception?.message ?: "Unknown error"
Log.d("Registration", "Registration failed: $message")
showToast(context, "Registration failed: $message")
onResult(false) // Регистрация не удалась
}
}
}
}
private fun signInUser(
context: Context,
auth: FirebaseAuth,
email: String,
password: String,
onResult: (Boolean) -> Unit
) {
auth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
// Вход успешный
Log.d("Registration", "User signed in successfully")
onResult(true) // Пользователь успешно вошел
} else {
// Вход не удался
val exception = task.exception
val message = exception?.message ?: "Unknown error"
Log.d("Registration", "Sign in failed: $message")
showToast(context, "Sign in failed: $message")
onResult(false) // Вход не удался
}
}
}
private fun showToast(context: Context, message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
И это важно в дальнейшем, если мы захотим получить данные пользователя или передать его фотографию или имя, то это можно будет сделать, написав этот код.
// в начале класса
private lateinit var googleAuthUiClient: GoogleAuthUiClient
override fun onAttach(context: Context) {
super.onAttach(context)
googleAuthUiClient = GoogleAuthUiClient(
context = requireContext(),
oneTapClient = Identity.getSignInClient(requireContext())
)
}
// и получить можно будет в любой части в этом класса
val id = ID(
userData = googleAuthUiClient.getSignedInUser()
) // uid пользователя
val name = UID(
userData = googleAuthUiClient.getSignedInUser()
) // имя
val img = IMG(
userData = googleAuthUiClient.getSignedInUser()
) // ссылка на картинку где лежит фото