EV Sum 2 uses Firebase Authentication to provide secure user authentication with email and password. The authentication system supports user registration, login, password recovery, and session management.
Architecture
The authentication feature follows a layered architecture:
AuthService (services/AuthService.kt:7) - Service layer that handles business logic
AuthRepository (data/repositories/AuthRepository.kt:11) - Data layer that communicates with Firebase
UI Screens - Login, Register, and Recover screens for user interaction
Key features
Email & Password Standard email and password authentication
Password Recovery Email-based password reset functionality
Session Management Real-time authentication state monitoring
Voice Input Speech-to-text for email and password entry
Implementation
AuthService
The AuthService class provides the main authentication methods:
class AuthService (
private val repo: AuthRepository = AuthRepository (),
) {
suspend fun login (email: String , password: String ) {
try {
repo. login (email, password)
} catch (e: Exception ) {
throw mapFirebaseAuthError (e)
}
}
suspend fun register (email: String , password: String ) {
try {
repo. register (email, password)
} catch (e: Exception ) {
throw mapFirebaseAuthError (e)
}
}
suspend fun recover (email: String ) {
try {
repo. sendPasswordReset (email)
} catch (e: Exception ) {
throw mapFirebaseAuthError (e)
}
}
fun logout () = repo. logout ()
fun isLoggedIn (): Boolean = repo. isLoggedIn ()
fun authStateFlow (): Flow < FirebaseUser ?> = repo. authStateFlow ()
fun currentEmail (): String ? = repo. currentEmail ()
}
AuthRepository
The repository layer interacts directly with Firebase Authentication:
class AuthRepository (
private val auth: FirebaseAuth = FirebaseAuth. getInstance (),
) {
suspend fun login (email: String , password: String ) {
auth. signInWithEmailAndPassword (email, password). await ()
auth.currentUser != null
}
suspend fun register (email: String , password: String ) {
auth. createUserWithEmailAndPassword (email, password). await ()
}
suspend fun sendPasswordReset (email: String ) {
auth. sendPasswordResetEmail (email). await ()
}
fun logout () {
FirebaseModule.auth. signOut ()
}
fun authStateFlow (): Flow < FirebaseUser ?> = callbackFlow {
val listener = FirebaseAuth. AuthStateListener {
fa -> trySend (fa.currentUser).isSuccess
}
auth. addAuthStateListener (listener)
trySend (auth.currentUser)
awaitClose { auth. removeAuthStateListener (listener) }
}
}
Usage examples
Login
Register
Password Recovery
Auth State
val authService = AuthService ()
val scope = rememberCoroutineScope ()
Button (
onClick = {
scope. launch {
try {
authService. login (email, password)
// Navigate to home screen
} catch (e: Exception ) {
// Show error message
}
}
}
) {
Text ( "Login" )
}
val authService = AuthService ()
val scope = rememberCoroutineScope ()
Button (
onClick = {
scope. launch {
try {
authService. register (email, password)
// Navigate to home screen
} catch (e: Exception ) {
// Show error message
}
}
}
) {
Text ( "Register" )
}
val authService = AuthService ()
val scope = rememberCoroutineScope ()
Button (
onClick = {
scope. launch {
try {
authService. recover (email)
// Show success message
} catch (e: Exception ) {
// Show error message
}
}
}
) {
Text ( "Send Reset Email" )
}
val authService = AuthService ()
LaunchedEffect (Unit) {
authService. authStateFlow (). collect { user ->
if (user == null ) {
// User logged out, navigate to login
} else {
// User logged in, navigate to home
}
}
}
Login screen implementation
The login screen (ui/auth/LoginScreen.kt:71) demonstrates how to use the authentication service:
Initialize services
Create instances of AuthService and set up state management: val authService = remember { AuthService () }
var email by rememberSaveable { mutableStateOf ( "" ) }
var password by rememberSaveable { mutableStateOf ( "" ) }
val scope = rememberCoroutineScope ()
Validate input
Validate email format and ensure fields are not empty: when {
cleanEmail. isBlank () || password. isBlank () -> {
message = "Complete your email and password"
isError = true
}
! cleanEmail. isBasicEmailValid () -> {
message = "Invalid email (e.g., [email protected] )"
isError = true
}
else -> {
// Proceed with login
}
}
Execute login
Call the authentication service in a coroutine: scope. launch {
try {
authService. login (cleanEmail, password)
message = null
isError = false
onLoginClick ()
} catch (e: Exception ) {
message = e.message ?: "Invalid credentials"
isError = true
}
}
The login screen supports voice input for both email and password fields using speech recognition with Spanish normalization.
Voice input uses the Speech-to-Text feature with specialized normalization for authentication fields.
val speechController = remember { SpeechController (context) }
var dictationTarget by remember { mutableStateOf (DictationTarget.EMAIL) }
speechController. setListener (
onPartial = { partial ->
when (dictationTarget) {
DictationTarget.EMAIL -> email = normalizeEmailFromSpeech (partial)
DictationTarget.PASSWORD -> password = normalizePasswordFromSpeech (partial)
}
},
onFinal = { final ->
when (dictationTarget) {
DictationTarget.EMAIL -> email = normalizeEmailFromSpeech ( final )
DictationTarget.PASSWORD -> password = normalizePasswordFromSpeech ( final )
}
}
)
Error handling
The authentication service maps Firebase errors to user-friendly messages:
private fun mapFirebaseAuthError (e: Exception ): Exception {
val msg = e.message ?: "Authentication error"
return Exception (msg)
}
Always handle authentication errors gracefully and provide clear feedback to users. Common errors include invalid credentials, network issues, and account already exists.
Session management
Check authentication state throughout your app:
val authService = AuthService ()
// Check if user is logged in
if (authService. isLoggedIn ()) {
// Show authenticated content
} else {
// Show login screen
}
// Get current user email
val email = authService. currentEmail ()
// Logout
authService. logout ()
Best practices
Always validate email format before attempting authentication: if ( ! email. isBasicEmailValid ()) {
// Show validation error
return
}
Minimum 6 characters (Firebase requirement)
Use PasswordVisualTransformation() for password fields
Never log or display passwords in plain text
Provide clear, actionable error messages:
“Complete your email and password” for empty fields
“Invalid email format” for malformed emails
“Invalid credentials” for authentication failures
Show loading indicators during authentication operations: var isLoading by remember { mutableStateOf ( false ) }
scope. launch {
isLoading = true
try {
authService. login (email, password)
} finally {
isLoading = false
}
}
Speech-to-Text Voice input for authentication fields
Phrase Management User-specific data storage with Firestore