Skip to main content

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

1

Select Start Date

Choose the beginning date for your image range using a date picker.
2

Select End Date

Pick the ending date to define your search range. The end date is optional.
3

Load Images

The app fetches all APOD images published between your selected dates.
4

Browse Results

Scroll through the chronological collection, with each image showing its title, date, and explanation.
5

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.

Date Format

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.
API returns an error if the date range is reversed.
Favorite operations require user authentication.

Developer Implementation

1

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")
    }
)
2

Inject ViewModel

@Composable
fun RangeImagesScreen(
    rangeImagesViewModel: RangeImagesViewModel = hiltViewModel()
) {
    // Implementation
}
3

Collect States

val dateRangeImages by rangeImagesViewModel.dateRangeImages.collectAsState()
val isLoading by rangeImagesViewModel.isLoading.collectAsState()
val favoriteStates by rangeImagesViewModel.favoriteStates.collectAsState()
4

Load Images

Button(
    onClick = {
        rangeImagesViewModel.loadDateRangeImages(startDate, endDate)
    }
) {
    Text("Load Images")
}
5

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)
                }
            }
        )
    }
}

Performance Considerations

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.

Build docs developers (and LLMs) love