Skip to main content

Overview

NASA Explorer integrates Firebase for backend services including user authentication and real-time database for storing favorite images. Firebase provides a comprehensive suite of tools that eliminates the need for custom backend infrastructure.
Firebase is a Backend-as-a-Service (BaaS) platform that provides authentication, databases, hosting, and more with minimal setup.

Firebase Services Used

Authentication

Email/password authentication for user accounts

Realtime Database

NoSQL cloud database for storing user favorites

Analytics

Track app usage and user behavior

Cloud Services

Automatic sync and offline support

Project Setup

Firebase is configured in the build files:
build.gradle.kts
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.jetbrains.kotlin.android)
    id("com.google.gms.google-services")  // Google services plugin
}

dependencies {
    // Firebase BoM (Bill of Materials) manages versions
    implementation(platform("com.google.firebase:firebase-bom:33.5.1"))
    
    // Firebase products (versions managed by BoM)
    implementation("com.google.firebase:firebase-analytics")
    implementation("com.google.firebase:firebase-auth-ktx")
    implementation(libs.firebase.database.ktx)
}
build.gradle.kts (root)
plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.jetbrains.kotlin.android) apply false
    id("com.google.gms.google-services") version "4.4.2" apply false
}
The Firebase BoM ensures all Firebase libraries use compatible versions. You don’t need to specify individual versions.

Hilt Modules for Firebase

Firebase Authentication Module

AuthNetworkModule.kt
package com.ccandeladev.nasaexplorer.data.auth

import com.google.firebase.auth.FirebaseAuth
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AuthNetworkModule {
    @Singleton
    @Provides
    fun provideFirebaseAuth() = FirebaseAuth.getInstance()
}

Firebase Database Module

FirebaseModule.kt
package com.ccandeladev.nasaexplorer.data.di

import com.google.firebase.database.FirebaseDatabase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object FirebaseModule {

    @Provides
    @Singleton
    fun provideFirebaseDatabase(): FirebaseDatabase {
        return FirebaseDatabase.getInstance()
    }
}

Firebase Authentication

Auth Service Implementation

The AuthService class encapsulates all Firebase Authentication operations:
AuthService.kt
package com.ccandeladev.nasaexplorer.data.auth

import com.google.firebase.auth.AuthResult
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.tasks.await
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

class AuthService @Inject constructor(
    private val firebaseAuth: FirebaseAuth
) {

    /**
     * Login user with email and password
     * Uses kotlinx-coroutines-play-services await() extension
     */
    suspend fun login(email: String, password: String): FirebaseUser? {
        return firebaseAuth.signInWithEmailAndPassword(email, password).await().user
    }

    /**
     * Register new user with email and password
     * Uses suspendCancellableCoroutine for manual control
     */
    suspend fun register(email: String, password: String): FirebaseUser? {
        return suspendCancellableCoroutine { cancellableContinuation ->
            firebaseAuth.createUserWithEmailAndPassword(email, password)
                .addOnSuccessListener { it: AuthResult? ->
                    val user: FirebaseUser? = it?.user
                    cancellableContinuation.resume(user)
                }
                .addOnFailureListener { it: Exception ->
                    cancellableContinuation.resumeWithException(it)
                }
        }
    }

    /**
     * Sign out the current user
     */
    fun userLogout() {
        firebaseAuth.signOut()
    }

    /**
     * Check if user is logged in
     */
    fun isUserLogged(): Boolean {
        return getCurrentUser() != null
    }

    /**
     * Get currently authenticated user
     */
    private fun getCurrentUser() = firebaseAuth.currentUser
}
Two approaches to convert Firebase callbacks to coroutines:
  1. await() extension from kotlinx-coroutines-play-services (simpler)
  2. suspendCancellableCoroutine for more control over cancellation

Usage in ViewModel

LoginScreenViewModel.kt
fun loginUser(email: String, password: String, onSuccess: () -> Unit) {
    viewModelScope.launch {
        _isLoading.value = true
        try {
            val user = authService.login(email, password)
            if (user != null) {
                onSuccess()
            }
        } catch (e: Exception) {
            _errorMessage.value = "Error al iniciar sesión: ${e.message}"
        } finally {
            _isLoading.value = false
        }
    }
}

Firebase Realtime Database

Saving Favorites

Users can save NASA images to their favorites:
DailyImageViewModel.kt
/**
 * Save a NASA image as favorite in Firebase Database
 */
fun saveToFavorites(nasaModel: NasaModel) {
    val userId = firebaseAuth.currentUser?.uid
    if (userId != null) {
        viewModelScope.launch {
            try {
                // Create new reference with auto-generated ID
                val favoriteRef = firebaseDatabase.reference
                    .child("favorites")
                    .child(userId)
                    .push()
                
                // Map of favorite data to save
                val favoriteImage = mapOf(
                    "id" to (favoriteRef.key ?: ""),      // Firebase-generated ID
                    "title" to nasaModel.title,            // Image title
                    "url" to nasaModel.url                 // Image URL
                )
                
                // Save data to Firebase and update favorite state
                favoriteRef.setValue(favoriteImage).await()
                _isFavorite.value = true
            } catch (e: Exception) {
                _errorMessage.value = "Error al guardar favorito ${e.message}"
            }
        }
    } else {
        _errorMessage.value = "Usuario no autenticado"
    }
}

Removing Favorites

DailyImageViewModel.kt
/**
 * Remove an image from favorites in Firebase
 */
fun removeFromFavorites(nasaModel: NasaModel) {
    val userId = firebaseAuth.currentUser?.uid
    if (userId != null) {
        viewModelScope.launch {
            try {
                val favoriteRef = firebaseDatabase.reference
                    .child("favorites")
                    .child(userId)
                
                // Find and delete the favorite by URL
                val snapshot = favoriteRef
                    .orderByChild("url")
                    .equalTo(nasaModel.url)
                    .get()
                    .await()
                
                for (child in snapshot.children) {
                    child.ref.removeValue().await()
                }
                
                _isFavorite.value = false
            } catch (e: Exception) {
                _errorMessage.value = "Error al buscar favorito ${e.message}"
            }
        }
    } else {
        _errorMessage.value = "Usuario no autenticado"
    }
}

Checking Favorite Status

DailyImageViewModel.kt
/**
 * Check if an image is favorited by the current user
 */
fun checkIsFavorite(url: String) {
    val userId = firebaseAuth.currentUser?.uid
    if (userId != null) {
        viewModelScope.launch {
            try {
                val favoriteRef = firebaseDatabase.reference
                    .child("favorites")
                    .child(userId)
                
                val snapshot = favoriteRef
                    .orderByChild("url")
                    .equalTo(url)
                    .get()
                    .await()
                
                // Update state based on whether URL exists
                _isFavorite.value = snapshot.exists()
            } catch (e: Exception) {
                _errorMessage.value = "Error al cargar estado de favoritos ${e.message}"
            }
        }
    } else {
        _errorMessage.value = "Usuario no autenticado"
    }
}

Database Structure

The Firebase Realtime Database structure for NASA Explorer:
{
  "favorites": {
    "user_id_1": {
      "favorite_key_1": {
        "id": "favorite_key_1",
        "title": "A Beautiful Galaxy",
        "url": "https://apod.nasa.gov/apod/image/2403/galaxy.jpg"
      },
      "favorite_key_2": {
        "id": "favorite_key_2",
        "title": "Nebula Cloud",
        "url": "https://apod.nasa.gov/apod/image/2403/nebula.jpg"
      }
    },
    "user_id_2": {
      "favorite_key_3": {
        "id": "favorite_key_3",
        "title": "Mars Rover View",
        "url": "https://apod.nasa.gov/apod/image/2403/mars.jpg"
      }
    }
  }
}

Favorite Icon State Management

UI updates based on favorite state:
DailyImageScreen.kt
@Composable
fun ImageItem(nasaModel: NasaModel, dailyImageViewModel: DailyImageViewModel) {
    val isFavorite by dailyImageViewModel.isFavorite.collectAsState()

    IconButton(onClick = {
        if (isFavorite) {
            dailyImageViewModel.removeFromFavorites(nasaModel = nasaModel)
        } else {
            dailyImageViewModel.saveToFavorites(nasaModel = nasaModel)
        }
    }) {
        Icon(
            imageVector = if (isFavorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder,
            contentDescription = "Favorites border",
            tint = if (isFavorite) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground
        )
    }
}

Coroutine Integration

Firebase operations use await() to convert callbacks to suspend functions:
// Before (callback-based):
favoriteRef.setValue(favoriteImage)
    .addOnSuccessListener { /* success */ }
    .addOnFailureListener { /* error */ }

// After (coroutine-based):
try {
    favoriteRef.setValue(favoriteImage).await()
    // success
} catch (e: Exception) {
    // error
}

Benefits in NASA Explorer

Firebase eliminates the need to write and maintain backend server code, authentication logic, and database management.
Changes to favorites sync instantly across devices when users are logged in.
Firebase SDK caches data locally, allowing the app to work offline and sync when connectivity returns.
Each user’s favorites are stored under their UID, ensuring data privacy and isolation.

Security Rules

For production, configure Firebase Database rules to secure user data:
{
  "rules": {
    "favorites": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid"
      }
    }
  }
}
Users can only read and write their own favorites. Never allow public read/write access in production.

Best Practices

1

Use BoM for Version Management

The Firebase BoM ensures compatible versions across all Firebase libraries.
2

Inject Firebase Instances

Use Hilt to provide Firebase instances as singletons for efficient resource usage.
3

Convert to Coroutines

Use await() to convert Firebase callbacks to suspend functions for cleaner code.
4

Handle Auth States

Always check firebaseAuth.currentUser before performing database operations.
5

Implement Security Rules

Configure proper security rules to protect user data in production.

Error Handling

Network Errors

Firebase operations can fail due to network issues - wrap in try-catch blocks

Auth Errors

Handle cases where user is not authenticated or token expires

Permission Errors

Database rules can reject operations - provide user-friendly error messages

Data Errors

Validate data before saving to prevent invalid entries

Firebase Console

Monitor and manage your app through the Firebase Console:
  • Authentication: View registered users and authentication methods
  • Realtime Database: Browse and edit database data
  • Analytics: Track user engagement and app usage
  • Crashlytics: Monitor app crashes and errors (if configured)

Resources

Firebase Documentation

Official Firebase documentation for all products

Firebase Android

Setup guide for Firebase on Android

Firebase Auth

Authentication documentation and best practices

Realtime Database

Realtime Database guide and API reference

Build docs developers (and LLMs) love