Как стать автором
Обновить

Google Регистрация в Android на kotlin + jetpack compose

Уровень сложностиПростой

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

Конечный результат
Конечный результат
Результат после регистрации
Результат после регистрации

Сначала нам нужно в Android Studio подключить наш проект к Firebase.

В верхнем меню находим Tools --> Firebase --> Authentication --> Authentication using Google.

 1 пункт сверху
1 пункт сверху
После создания проекта в Fierbase не забывайте добавить SDK
После создания проекта в Fierbase не забывайте добавить SDK

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

Нам нужен только пока Google в разделе sing-in method
Нам нужен только пока Google в разделе sing-in method

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

Снизу будет кнопка add finger
Снизу будет кнопка add finger

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

Не забываем, если у вас не открыт 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 для нашего приложения. Скачиваем его и добавляем в корень нашего проекта. как на фото

Выходим из Android view и переключаемся на Project view. В папке app добавляем наш google-services.json.
Выходим из Android view и переключаемся на Project view. В папке app добавляем наш google-services.json.

Также не забудьте добавить конфигурацию Web SDK.

и спуститесь чуть ниже
и спуститесь чуть ниже
Скопируйте свой web id
Скопируйте свой web id

Как только скопировали его, не забудьте в ресурсных файлах скопировать и вписать в 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()
      ) // ссылка на картинку где лежит фото 

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.