Привет всем! Это моя первая статья на 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() ) // ссылка на картинку где лежит фото
