Implement Firebase Google sign-in with enum-based auth contracts.
Refactor AuthService to use AuthProvider and User, add Firebase-backed auth wiring for login/manage flows, and fix app-level Google services configuration so Credential Manager sign-in works reliably. Made-with: Cursor
This commit is contained in:
@@ -2,7 +2,6 @@ plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.apollo)
|
||||
alias(libs.plugins.google.services)
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -50,6 +49,7 @@ dependencies {
|
||||
// Firebase
|
||||
implementation(platform(libs.firebase.bom))
|
||||
implementation(libs.firebase.analytics)
|
||||
implementation(libs.firebase.auth)
|
||||
implementation(libs.firebase.crashlytics)
|
||||
implementation(libs.firebase.messaging)
|
||||
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "223884730019",
|
||||
"project_id": "db3-airmq-debug",
|
||||
"storage_bucket": "db3-airmq-debug.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:223884730019:android:ebe25591cd4c41c2562916",
|
||||
"android_client_info": {
|
||||
"package_name": "org.db3.airmq"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyCcgdvdp8xec2dKUqlQHl9peQAyOWRVga4"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "223884730019-1hkbacov3tem4snsih7vs218lt9ppvde.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:223884730019:android:1345a64a13459de7562916",
|
||||
"android_client_info": {
|
||||
"package_name": "org.db3.airmq.debug"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "223884730019-0cobqn6haoga9n5o60lqcke9uglqihea.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "org.db3.airmq.debug",
|
||||
"certificate_hash": "38547bbc27af278e820c78fc012227a40f64505f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "223884730019-p44o1v2t3g6v0kmopsa65f0umf60ksif.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "org.db3.airmq.debug",
|
||||
"certificate_hash": "195356c67cabf18c9ab0a8ccfcec45c3346b6b6c"
|
||||
}
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyCcgdvdp8xec2dKUqlQHl9peQAyOWRVga4"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "223884730019-1hkbacov3tem4snsih7vs218lt9ppvde.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package org.db3.airmq.sdk.auth
|
||||
|
||||
import org.db3.airmq.sdk.auth.model.Auth
|
||||
import org.db3.airmq.sdk.auth.model.AuthProvider
|
||||
import org.db3.airmq.sdk.auth.model.User
|
||||
|
||||
interface AuthService {
|
||||
suspend fun getCurrentSession(): Auth?
|
||||
suspend fun getCurrentSession(): User?
|
||||
suspend fun isAuthenticated(): Boolean
|
||||
suspend fun signIn(provider: String, token: String? = null): Result<Auth>
|
||||
suspend fun signIn(provider: AuthProvider, token: String): Result<User>
|
||||
suspend fun signOut(): Result<Unit>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.db3.airmq.sdk.auth
|
||||
|
||||
import com.google.android.gms.tasks.Task
|
||||
import com.google.firebase.auth.FirebaseAuth
|
||||
import com.google.firebase.auth.FirebaseUser
|
||||
import com.google.firebase.auth.GoogleAuthProvider
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.db3.airmq.sdk.auth.model.AuthProvider
|
||||
import org.db3.airmq.sdk.auth.model.User
|
||||
|
||||
@Singleton
|
||||
class FirebaseAuthService @Inject constructor(
|
||||
private val firebaseAuth: FirebaseAuth
|
||||
) : AuthService {
|
||||
|
||||
override suspend fun getCurrentSession(): User? = firebaseAuth.currentUser?.toUser()
|
||||
|
||||
override suspend fun isAuthenticated(): Boolean = firebaseAuth.currentUser != null
|
||||
|
||||
override suspend fun signIn(provider: AuthProvider, token: String): Result<User> = runCatching {
|
||||
require(token.isNotBlank()) { "ID token is required for Google sign-in." }
|
||||
when (provider) {
|
||||
AuthProvider.GOOGLE -> signInWithGoogle(token)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun signOut(): Result<Unit> = runCatching {
|
||||
firebaseAuth.signOut()
|
||||
}
|
||||
|
||||
private suspend fun signInWithGoogle(token: String): User {
|
||||
val credential = GoogleAuthProvider.getCredential(token, null)
|
||||
val authResult = firebaseAuth.signInWithCredential(credential).awaitResult()
|
||||
val firebaseUser = authResult.user ?: error("Google sign-in completed without a Firebase user.")
|
||||
return firebaseUser.toUser()
|
||||
}
|
||||
|
||||
private fun FirebaseUser.toUser(): User = User(
|
||||
userId = uid,
|
||||
email = email,
|
||||
displayName = displayName,
|
||||
isAuthenticated = true
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun <T> Task<T>.awaitResult(): T = suspendCancellableCoroutine { continuation ->
|
||||
addOnSuccessListener { result ->
|
||||
continuation.resume(result)
|
||||
}
|
||||
addOnFailureListener { exception ->
|
||||
continuation.resumeWithException(exception)
|
||||
}
|
||||
addOnCanceledListener {
|
||||
continuation.cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.db3.airmq.sdk.auth.model
|
||||
|
||||
enum class AuthProvider {
|
||||
GOOGLE
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.db3.airmq.sdk.auth.model
|
||||
|
||||
data class Auth(
|
||||
data class User(
|
||||
val userId: String,
|
||||
val email: String?,
|
||||
val displayName: String?,
|
||||
@@ -1,12 +1,15 @@
|
||||
package org.db3.airmq.sdk.di
|
||||
|
||||
import com.apollographql.apollo.ApolloClient
|
||||
import com.google.firebase.auth.FirebaseAuth
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
import org.db3.airmq.sdk.auth.AuthService
|
||||
import org.db3.airmq.sdk.auth.FirebaseAuthService
|
||||
import org.db3.airmq.sdk.map.MapServiceImpl
|
||||
import org.db3.airmq.sdk.map.MapService
|
||||
import org.db3.airmq.sdk.settings.SettingsService
|
||||
@@ -26,11 +29,19 @@ object SDKModule {
|
||||
.addInterceptor(ApolloLoggingInterceptor())
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFirebaseAuth(): FirebaseAuth = FirebaseAuth.getInstance()
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class SDKBindModule {
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindAuthService(impl: FirebaseAuthService): AuthService
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMapService(impl: MapServiceImpl): MapService
|
||||
|
||||
Reference in New Issue
Block a user