La plupart des applications ont une sorte d’authentification. Pour cet article, nous verrons comment ce flux fonctionne en utilisant la connexion One Tap de Google, Firebase et Amity.
La pile technologique que nous utiliserons est :
- Script Kotlin (KTS) pour notre Gradle
- Jetpack Compose pour notre interface utilisateur
- Architecture MVVM
- Poignée pour l’injection de dépendance
- SDK social d’Amity
- Authentification OneTap de Google
- Authentification Firebase
Le déroulement est le suivant : premièrement, nous vérifions si la session d’Amity est valide ; si c’est le cas, nous continuerons avec notre flux principal ; sinon, nous redirigerons nos utilisateurs vers notre écran de connexion. Là, nous essaierons d’abord de connecter nos utilisateurs à l’aide de Google. S’ils se sont déjà connectés à notre application, cela réussira, et nous pourrons alors nous connecter à l’aide de Firebase. Sinon, nous devons d’abord inscrire nos utilisateurs à notre application, puis continuer avec Firebase.
Configuration
Bon, commençons ! Tout d’abord, pour configurer notre projet, nous allons utiliser le guide officiel. Puisque nous n’utilisons pas Groovy, les dépendances sont ajoutées, comme indiqué ci-dessous.
plugins {
...
id("com.google.gms.google-services") version "4.3.14"
}
dependencies {
...
implementation("com.google.android.gms:play-services-auth:20.4.0")
}
apply(plugin = "com.google.gms.google-services")
* Alors que vous devez utiliser le fourni libs.versions
file (catalogue des versions), pour des raisons de lisibilité du code, dans cet article, nous ajouterons les versions comme celle-ci.
Puisque nous sommes ici, nous ajouterons également les dépendances Amity et Firebase dont nous aurons besoin plus tard. Malheureusement, l’ajout de dépendances Firebase dans le modèle ne peut pas encore être effectué via l’assistant d’Android Studio sans lever d’exceptions, nous allons donc les ajouter manuellement.
dependencies {
...
// Amity
implementation("com.github.AmityCo.Amity-Social-Cloud-SDK-Android:amity-sdk:5.33.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.6.4")
// Firebase
implementation(platform("com.google.firebase:firebase-bom:31.1.1"))
implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-auth-ktx")
implementation("com.google.android.gms:play-services-auth:20.4.0")
implementation("com.google.firebase:firebase-firestore-ktx")
}
Notre dernière étape consiste à configurer notre console Firebase. Selon la documentation officielle :
Pour utiliser un fournisseur d’authentification, vous devez l’activer dans la console Firebase. Accédez à la page Méthode de connexion dans la section Authentification Firebase pour activer la connexion par e-mail/mot de passe et tout autre fournisseur d’identité que vous souhaitez pour votre application.
Nous allons bien sûr activer la connexion Google. Ensuite, nous allons accéder aux paramètres du projet et ajouter notre empreinte digitale de certificat SHA.
Nous sommes maintenant prêts à commencer à ajouter notre code de connexion ! Nous créons d’abord les fichiers dont nous aurons besoin : MainNavigation
, MainViewModel
, LoginScreen
, LoginViewModel
, AuthRepository
.
Observation de l’état de l’authentification
Notre premier Composable à s’appeler est le MainNavigation
. Pour vérifier en permanence la validité de la session d’Amity, c’est ici que nous surveillerons son état. Puisque nous voulons que l’état de notre application reflète l’état de la session, nous allons le mapper à un StateFlow
.
StateFlow est un flux observable de détenteur d’état qui émet les mises à jour d’état actuelles et nouvelles à ses collecteurs. La valeur de l’état actuel peut également être lue via sa propriété value. Dans Android, StateFlow convient parfaitement aux classes qui doivent maintenir un état mutable observable.
Nous initions et obtenons le SessionState
dans le AuthRepository
puis dans notre MainViewModel
nous convertissons notre flow
à un StateFlow
et enfin, dans notre MainNavigation
on l’observe.
override val amitySession = flow {
emit(AmityCoreClient.currentSessionState)
AmityCoreClient.observeSessionState().asFlow()
}
val uiState: StateFlow<MainUiState> = authRepository
.amitySession.map {
when(it) {
SessionState.NotLoggedIn,
SessionState.Establishing -> MainUiState.LoggedOut
SessionState.Established,
SessionState.TokenExpired -> MainUiState.LoggedIn
is SessionState.Terminated -> MainUiState.Banned
}
}
.catch { Error(it) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), MainUiState.Loading) LaunchedEffect(lifecycleOwner) {
viewModel.uiState.collect { state ->
when(state) {
MainUiState.Banned -> {} //TODO snackbar
is MainUiState.Error -> {} //TODO snackbar
MainUiState.Loading -> { /* no-op */ }
MainUiState.LoggedIn -> navController.navigate("main") { popUpTo(0) }
MainUiState.LoggedOut -> navController.navigate("login") { popUpTo(0) }
}
}
}
LaunchedEffect(lifecycleOwner) {
viewModel.uiState.collect { state ->
when(state) {
MainUiState.Banned -> showSnackbar(scope, snackbarHostState, userBannedText)
is MainUiState.Error -> showSnackbar(scope, snackbarHostState, userErrorText)
MainUiState.Loading -> { /* no-op */ }
MainUiState.LoggedIn -> navController.navigate(Route.UsersList.route) { popUpTo(0) }
MainUiState.LoggedOut -> navController.navigate(Route.Login.route) { popUpTo(0) }
}
}
}
Cool, maintenant nous pouvons observer l’état de nos utilisateurs !
Connexion Google OneTap
Nos utilisateurs voient maintenant notre page de connexion brillante, qui, pour l’instant, est simplement la suivante :
@Composable
fun LoginScreen(
modifier: Modifier = Modifier,
navigateToUsers: () -> Unit,
viewModel: LoginViewModel = hiltViewModel()
) {
Column(modifier) {
Button(onClick = { /* TODO */ }) {
Text(text = stringResource(R.string.login_google_bt))
}
}
}
Pas si brillant, après tout.

Pour commencer, nous avons besoin de notre appel de connexion dans notre AuthRepository
. *
* :
Nous utilisons une interface pour encapsuler notre AuthRepository
fonctions de. Cela sera particulièrement utile pour créer une maquette AuthRepository
pour nos prochains tests.
Notre signInRequest
, SignUpRequest
ainsi que les clients Firebase et Google sont fournis à nos AuthRepository
mise en œuvre lors de l’injection de dépendance, comme indiqué ci-dessous :
@Module
@InstallIn(SingletonComponent::class)
class AuthModule {
private val signInRequest = BeginSignInRequest.builder()
.setGoogleIdTokenRequestOptions(
BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
.setSupported(true)
.setServerClientId(BuildConfig.SERVER_CLIENT_ID)
// Only show accounts previously used to sign in.
.setFilterByAuthorizedAccounts(true)
.build())
// Automatically sign in when exactly one credential is retrieved.
.setAutoSelectEnabled(true)
.build()
private val signUpRequest = BeginSignInRequest.builder()
.setGoogleIdTokenRequestOptions(
BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
.setSupported(true)
.setServerClientId(BuildConfig.SERVER_CLIENT_ID)
.setFilterByAuthorizedAccounts(false)
.build())
.build()
@Provides
@Singleton
fun provideAuthRepository(@ApplicationContext appContext: Context) : AuthRepository {
return AuthRepositoryImp(
Identity.getSignInClient(appContext),
signInRequest,
signUpRequest,
Firebase.auth,
Firebase.firestore
)
}
}
Retour à notre flux de connexion ! Comme mentionné ci-dessus, si c’est la première fois que nos utilisateurs tentent de se connecter avec ce compte, cela déclenchera une exception. Pour éviter d’afficher de faux messages d’erreur à l’utilisateur, lorsque nous recevons une exception dans notre méthode de connexion, nous essayons de nous inscrire. Si cela réussit, nous passons à autre chose ; sinon, nous traitons alors l’erreur.
//AuthRepositoryImpl.kt
override suspend fun signInWithGoogle(): OneTapResponse {
return try {
val result = oneTapClient.beginSignIn(signInRequest).await()
ApiResponse.Success(result)
} catch (e: Exception) {
ApiResponse.Failure(e)
}
}
override suspend fun signUpWithGoogle(): OneTapResponse {
return try {
val result = oneTapClient.beginSignIn(signUpRequest).await()
ApiResponse.Success(result)
} catch (e: Exception) {
ApiResponse.Failure(e)
}
}
//LoginViewModel
suspend fun googleSignIn(launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>) {
when (val oneTapResponse: ApiResponse<BeginSignInResult> =
authRepository.signInWithGoogle()) {
is ApiResponse.Success -> {
val result = oneTapResponse.data!!
val intent = IntentSenderRequest.Builder(result.pendingIntent.intentSender).build()
launcher.launch(intent)
}
is ApiResponse.Loading -> { /* no-op */ }
else -> {
// No saved credentials found. Launch the One Tap sign-up flow
googleSignUp(launcher)
}
}
}
private suspend fun googleSignUp(launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>) {
when(val oneTapResponse: ApiResponse<BeginSignInResult> = authRepository.signUpWithGoogle()) {
is ApiResponse.Success -> {
val result = oneTapResponse.data!!
val intent = IntentSenderRequest.Builder(result.pendingIntent.intentSender).build()
launcher.launch(intent)
}
is ApiResponse.Loading -> { /* no-op */ }
else -> handleSignUpError()
}
}
Si nous utilisions Views au lieu de Compose, nous gérerions le résultat de l’intention dans notre onActivityForResult
méthode. Au lieu de cela, nous utiliserons le ManagedActivityResultLauncher
dans notre LoginScreen
. Si notre lanceur revient avec un résultat positif, nous obtiendrons alors l’identifiant de notre utilisateur et passerons à la connexion Firebase.
@Composable
fun LoginScreen(
modifier: Modifier = Modifier,
viewModel:...