Pull to refresh

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

Level of difficultyEasy

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

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.