Overview
The Date Range feature enables users to retrieve multiple NASA Astronomy Pictures of the Day within a specified date range. This is perfect for exploring themed collections, historical periods, or browsing all images from a specific month or year.
User Experience
Select Start Date
Choose the beginning date for your image range using a date picker.
Select End Date
Pick the ending date to define your search range. The end date is optional.
Load Images
The app fetches all APOD images published between your selected dates.
Browse Results
Scroll through the chronological collection, with each image showing its title, date, and explanation.
Manage Favorites
Each image displays its favorite status. Tap the icon to save or remove from your collection.
The NASA APOD API returns images in chronological order when fetching date ranges.
API Integration
NASA API Endpoint
app/src/main/java/com/ccandeladev/nasaexplorer/data/api/NasaApiService.kt
interface NasaApiService {
@GET ( "planetary/apod" )
suspend fun getImagesInRange (
@Query ( "api_key" ) apiKey: String ,
@Query ( "start_date" ) startDate: String ,
@Query ( "end_date" ) endDate: String ? = null
): List < NasaResponse >
}
Parameters:
api_key: NASA API authentication key
start_date: Starting date in YYYY-MM-DD format (required)
end_date: Ending date in YYYY-MM-DD format (optional, defaults to today)
Returns: List of NasaResponse objects for each day in the range.
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 getImagesInRange (startDate: String , endDate: String ? = null ): List < NasaModel > {
val response = nasaApiService. getImagesInRange (
apiKey = API_KEY,
startDate = startDate,
endDate = endDate
)
return response. map { it. toNasaModel () }
}
}
If endDate is omitted, the API returns images from startDate to the current date.
ViewModel Architecture
RangeImagesViewModel
Manages date range images and their favorite states:
app/src/main/java/com/ccandeladev/nasaexplorer/ui/rangeimagesscreen/RangeImagesViewModel.kt
@HiltViewModel
class RangeImagesViewModel @Inject constructor (
private val nasaRepository: NasaRepository ,
private val firebaseAuth: FirebaseAuth ,
private val firebaseDatabase: FirebaseDatabase
) : ViewModel () {
private val _dateRangeImages = MutableStateFlow < List < NasaModel >>( emptyList ())
val dateRangeImages: StateFlow < List < NasaModel >> = _dateRangeImages
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 favoriteStates: StateFlow < Map < String , Boolean >> = _favoriteStates
fun loadDateRangeImages (startDate: String , endDate: String ) {
viewModelScope. launch {
_isLoading. value = true
try {
val results = nasaRepository. getImagesInRange (
startDate = startDate,
endDate = endDate
)
_dateRangeImages. 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"
_dateRangeImages. value = emptyList ()
} finally {
_isLoading. value = false
}
}
}
}
State Management
dateRangeImages List of NasaModel objects for all images in the selected date range.
isLoading Boolean indicating active API request status.
errorMessage Error message string for network or validation failures.
favoriteStates Map tracking favorite status for each image by URL.
Favorites Management
Save to Favorites
app/src/main/java/com/ccandeladev/nasaexplorer/ui/rangeimagesscreen/RangeImagesViewModel.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 favoritos ${ e.message } "
}
}
} else {
_errorMessage. value = "Usuario no autenticado"
}
}
Remove from Favorites
app/src/main/java/com/ccandeladev/nasaexplorer/ui/rangeimagesscreen/RangeImagesViewModel.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 guardar favoritos ${ e.message } "
}
}
} else {
_errorMessage. value = "Usuario no autenticado"
}
}
Check Favorites Status
app/src/main/java/com/ccandeladev/nasaexplorer/ui/rangeimagesscreen/RangeImagesViewModel.kt
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 guardar favoritos ${ e.message } "
}
}
} else {
_errorMessage. value = "Usuario no autenticado"
}
}
The checkFavorites() function is automatically called after loading date range images to populate favorite icon states.
All dates must follow the YYYY-MM-DD format:
// Correct format
viewModel. loadDateRangeImages ( "2024-01-01" , "2024-01-31" )
// Incorrect - will cause API error
viewModel. loadDateRangeImages ( "01/01/2024" , "01/31/2024" )
The NASA APOD archive starts on June 16, 1995. Requesting dates before this will return an error.
Use Cases
Monthly Collection Fetch all images from a specific month: loadDateRangeImages ( "2024-03-01" , "2024-03-31" )
Year Overview Browse an entire year: loadDateRangeImages ( "2023-01-01" , "2023-12-31" )
Custom Period Any arbitrary date range: loadDateRangeImages ( "2024-06-15" , "2024-07-04" )
From Date to Today Omit end date for “up to now”: loadDateRangeImages ( "2024-01-01" , "" )
Firebase Database Structure
favorites/
└── {userId}/
├── {pushId1}/
│ ├── id: "{pushId1}"
│ ├── title: "2024-01-15 Image"
│ └── url: "https://..."
└── {pushId2}/
├── id: "{pushId2}"
├── title: "2024-01-20 Image"
└── url: "https://..."
Error Handling
Displays connection error message and clears the image list.
API will return an error if dates aren’t in YYYY-MM-DD format.
Dates before June 16, 1995 or future dates will cause API errors.
End Date Before Start Date
API returns an error if the date range is reversed.
Favorite operations require user authentication.
Developer Implementation
Date Input UI
Implement date pickers for start and end dates: var startDate by remember { mutableStateOf ( "" ) }
var endDate by remember { mutableStateOf ( "" ) }
DatePickerDialog (
onDateSelected = { selectedDate ->
startDate = selectedDate. format ( "yyyy-MM-dd" )
}
)
Inject ViewModel
@Composable
fun RangeImagesScreen (
rangeImagesViewModel: RangeImagesViewModel = hiltViewModel ()
) {
// Implementation
}
Collect States
val dateRangeImages by rangeImagesViewModel.dateRangeImages. collectAsState ()
val isLoading by rangeImagesViewModel.isLoading. collectAsState ()
val favoriteStates by rangeImagesViewModel.favoriteStates. collectAsState ()
Load Images
Button (
onClick = {
rangeImagesViewModel. loadDateRangeImages (startDate, endDate)
}
) {
Text ( "Load Images" )
}
Display Results
LazyColumn {
items (dateRangeImages) { image ->
val isFavorite = favoriteStates[image.url] ?: false
ImageCard (
nasaModel = image,
isFavorite = isFavorite,
onFavoriteClick = {
if (isFavorite) {
rangeImagesViewModel. removeFromFavorites (image)
} else {
rangeImagesViewModel. saveToFavorites (image)
}
}
)
}
}
Large date ranges can return many images. Consider implementing pagination or limiting the maximum range to avoid performance issues and API rate limits.
Recommended Practices:
Limit initial ranges to 1 month or less
Show a warning for ranges exceeding 100 days
Implement lazy loading for large result sets
Cache results locally to reduce API calls
API Rate Limits
NASA API has rate limits:
Standard API Key : 1,000 requests per hour
Consider the size of your date ranges when planning features
Implement appropriate error handling for rate limit responses
For production apps, consider caching date range results or implementing incremental loading to stay within rate limits.