Overview
The Random Images feature allows users to explore NASA’s vast Astronomy Picture of the Day archive by fetching multiple random images at once. This provides a serendipitous way to discover astronomical wonders from any point in APOD history.
User Experience
Load Random Images
The screen fetches 5 random images from NASA’s APOD archive when launched.
Browse Collection
Scroll through the random selection, each displaying its title, date, and full explanation.
Manage Favorites
Each image has a favorite icon that reflects its saved state. Tap to add or remove from favorites.
Refresh for More
Load a new set of random images to continue exploring.
By default, the feature loads 5 random images per request, but this count is configurable.
API Integration
NASA API Endpoint
app/src/main/java/com/ccandeladev/nasaexplorer/data/api/NasaApiService.kt
interface NasaApiService {
@GET ( "planetary/apod" )
suspend fun getRandomImages (
@Query ( "api_key" ) apiKey: String ,
@Query ( "count" ) count: Int ,
): List < NasaResponse >
}
Parameters:
api_key: NASA API authentication key
count: Number of random images to fetch (typically 5)
Returns: List of NasaResponse objects with random images from the APOD archive.
Repository Implementation
app/src/main/java/com/ccandeladev/nasaexplorer/data/api/NasaRepository.kt
class NasaRepository @Inject constructor ( private val nasaApiService: NasaApiService ) {
companion object {
private const val API_KEY = BuildConfig.NASA_API_KEY
}
suspend fun getRandomImages (count: Int ): List < NasaModel > {
val response = nasaApiService. getRandomImages (apiKey = API_KEY, count = count)
return response. map { it. toNasaModel () }
}
}
The repository automatically transforms each NasaResponse into a NasaModel for UI consumption.
ViewModel Architecture
RandomImageViewModel
Manages state and business logic for random images:
app/src/main/java/com/ccandeladev/nasaexplorer/ui/randomimagescreen/RandomImageViewModel.kt
@HiltViewModel
class RandomImageViewModel @Inject constructor (
private val nasaRepository: NasaRepository ,
private val firebaseAuth: FirebaseAuth ,
private val firebaseDatabase: FirebaseDatabase
) : ViewModel () {
private val _randomImages = MutableStateFlow < List < NasaModel >>( emptyList ())
val randomImages: StateFlow < List < NasaModel >> = _randomImages
private val _errorMessage = MutableStateFlow < String ?>( null )
val errorMessage: StateFlow < String ?> = _errorMessage
private val _isLoading = MutableStateFlow ( false )
val isLoading: StateFlow < Boolean > = _isLoading
private val _favoriteStates = MutableStateFlow < Map < String , Boolean >>( emptyMap ())
val favoriteState: StateFlow < Map < String , Boolean >> = _favoriteStates
fun loadRandomImages (count: Int = 5 ) {
viewModelScope. launch {
_isLoading. value = true
try {
val results = nasaRepository. getRandomImages (count = count)
_randomImages. value = results
_errorMessage. value = null
checkFavorites (results. map { it.url })
} catch (e: Exception ) {
_errorMessage. value = "Sin conexión a internet. Conéctate a una red Wi-Fi o habilita datos móviles para ver las imágenes"
_randomImages. value = emptyList ()
} finally {
_isLoading. value = false
}
}
}
}
State Management
The ViewModel exposes four reactive states:
randomImages List of NasaModel objects representing the fetched random images.
isLoading Boolean indicating whether images are currently being fetched.
errorMessage Error message string for network or API failures.
favoriteStates Map of URL to Boolean, tracking favorite status for each image.
Favorites Management
Tracking Multiple Favorites
Unlike the daily image screen, random images require tracking multiple favorite states simultaneously:
app/src/main/java/com/ccandeladev/nasaexplorer/ui/randomimagescreen/RandomImageViewModel.kt
private val _favoriteStates = MutableStateFlow < Map < String , Boolean >>( emptyMap ())
val favoriteState: StateFlow < Map < String , Boolean >> = _favoriteStates
The map uses image URLs as keys and boolean values to indicate favorite status.
Save to Favorites
app/src/main/java/com/ccandeladev/nasaexplorer/ui/randomimagescreen/RandomImageViewModel.kt
fun saveToFavorites (nasaModel: NasaModel ) {
val userId = firebaseAuth.currentUser?.uid
if (userId != null ) {
viewModelScope. launch {
try {
val favoriteRef = firebaseDatabase.reference
. child ( "favorites" )
. child (userId)
. push ()
val favoriteImage = mapOf (
"id" to (favoriteRef.key ?: "" ),
"title" to nasaModel.title,
"url" to nasaModel.url
)
favoriteRef. setValue (favoriteImage). await ()
val currentState = _favoriteStates. value . toMutableMap ()
currentState[nasaModel.url] = true
_favoriteStates. value = currentState
} catch (e: Exception ) {
_errorMessage. value = "Error al guardar favorito ${ e.message } "
}
}
} else {
_errorMessage. value = "Usuario no autenticado"
}
}
Remove from Favorites
app/src/main/java/com/ccandeladev/nasaexplorer/ui/randomimagescreen/RandomImageViewModel.kt
fun removeFromFavorites (nasaModel: NasaModel ) {
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 (nasaModel.url)
. get ()
. await ()
for (child in snapshot.children) {
child.ref. removeValue (). await ()
}
val currentState = _favoriteStates. value . toMutableMap ()
currentState[nasaModel.url] = false
_favoriteStates. value = currentState
} catch (e: Exception ) {
_errorMessage. value = "Error al borrar favorito ${ e.message } "
}
}
}
}
Check Favorites Status
Automatically checks which images are already favorited:
app/src/main/java/com/ccandeladev/nasaexplorer/ui/randomimagescreen/RandomImageViewModel.kt
private fun checkFavorites (urls: List < String >) {
val userId = firebaseAuth.currentUser?.uid
if (userId != null ) {
viewModelScope. launch {
try {
val favoriteRef = firebaseDatabase.reference
. child ( "favorites" )
. child (userId)
val snapshot = favoriteRef. get (). await ()
val currentState = _favoriteStates. value . toMutableMap ()
for (url in urls) {
currentState[url] = snapshot.children. any {
it. child ( "url" ). value == url
}
}
_favoriteStates. value = currentState
} catch (e: Exception ) {
_errorMessage. value = "Error al cargar estado de favorito ${ e.message } "
}
}
}
}
The checkFavorites() function is automatically called after images load, ensuring favorite icons display correct state immediately.
UI Implementation
Accessing favorite state for a specific image:
val randomImages by viewModel.randomImages. collectAsState ()
val favoriteStates by viewModel.favoriteState. collectAsState ()
randomImages. forEach { image ->
val isFavorite = favoriteStates[image.url] ?: false
// Display image with favorite button
ImageCard (
nasaModel = image,
isFavorite = isFavorite,
onFavoriteClick = {
if (isFavorite) {
viewModel. removeFromFavorites (image)
} else {
viewModel. saveToFavorites (image)
}
}
)
}
Firebase Database Structure
Random images share the same favorites structure as other features:
favorites/
└── {userId}/
├── {pushId1}/
│ ├── id: "{pushId1}"
│ ├── title: "Random Image 1"
│ └── url: "https://..."
└── {pushId2}/
├── id: "{pushId2}"
├── title: "Random Image 2"
└── url: "https://..."
Error Handling
Displays network error message and resets image list to empty.
Caught and displayed via the errorMessage state flow.
Shows authentication error when attempting favorite operations without login.
All Firebase operations are wrapped in try-catch blocks with specific error messages.
Developer Guide
Inject ViewModel
@Composable
fun RandomImageScreen (
randomImageViewModel: RandomImageViewModel = hiltViewModel ()
) {
// Implementation
}
Collect States
val randomImages by randomImageViewModel.randomImages. collectAsState ()
val isLoading by randomImageViewModel.isLoading. collectAsState ()
val favoriteStates by randomImageViewModel.favoriteState. collectAsState ()
Load Images
LaunchedEffect (Unit) {
randomImageViewModel. loadRandomImages (count = 5 )
}
Handle Favorite Actions
IconButton (
onClick = {
if (favoriteStates[image.url] == true ) {
randomImageViewModel. removeFromFavorites (image)
} else {
randomImageViewModel. saveToFavorites (image)
}
}
) {
Icon (
imageVector = if (favoriteStates[image.url] == true )
Icons.Filled.Star else Icons.Outlined.Star,
contentDescription = "Favorite"
)
}
Always check for null values when accessing the favoriteStates map, as images may not have a favorite state entry yet.
The default count of 5 images balances discovery with API rate limits
Favorite status is checked in a single Firebase query for all loaded images
State updates use immutable map operations to ensure proper UI reactivity
Random images are selected by NASA’s API from the entire APOD archive, so you may occasionally see the same image across different loads.