NASA Explorer follows the Model-View-ViewModel (MVVM) architectural pattern, which provides clear separation between UI code and business logic. This pattern is the recommended approach for Android apps using Jetpack Compose.
Models represent the core business entities and are independent of any framework:
domain/NasaModel.kt
package com.ccandeladev.nasaexplorer.domain// Modelo contiene solo los datos que se usaran en la UIdata class NasaModel( val title: String, val url: String, val date: String, val explanation: String,)
The domain model is decoupled from the API response format. This separation allows the API to change without affecting the rest of the application.
The repository layer transforms API responses into domain models:
data/api/NasaRepository.kt
class NasaRepository @Inject constructor( private val nasaApiService: NasaApiService) { companion object { private const val API_KEY = BuildConfig.NASA_API_KEY } // Obtener imagen del día suspend fun getImageOfTheDay(date: String? = null): NasaModel { val response = nasaApiService.getImageOfTheDay( apiKey = API_KEY, date = date ) return response.toNasaModel() // Convierte la respuesta a NasaModel } // Obtener imágenes en un rango de fechas 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() } } // Obtener imágenes aleatorias suspend fun getRandomImages(count: Int): List<NasaModel> { val response = nasaApiService.getRandomImages( apiKey = API_KEY, count = count ) return response.map { it.toNasaModel() } }}
@HiltViewModelclass DailyImageViewModel @Inject constructor( private val nasaRepository: NasaRepository, private val firebaseAuth: FirebaseAuth, private val firebaseDatabase: FirebaseDatabase) : ViewModel() { // Almacena la imagen diaria. Inicia en null private val _dailyImage = MutableStateFlow<NasaModel?>(null) val dailyImage: StateFlow<NasaModel?> = _dailyImage // Estado para manejar mensajes de error private val _errorMessage = MutableStateFlow<String?>(null) val errorMessage: StateFlow<String?> = _errorMessage // Controlar el estado de la carga de la imagen private val _isLoading = MutableStateFlow(false) val isLoading: StateFlow<Boolean> = _isLoading // Controlar el estado del icono de favoritos private val _isFavorite = MutableStateFlow<Boolean>(false) val isFavorite: StateFlow<Boolean> = _isFavorite /** * Cargar la imagen diaria * @param date opcion de cargar imagen en una fecha especificada */ fun loadDailyImage(date: String? = null) { viewModelScope.launch { _isLoading.value = true try { val result = nasaRepository.getImageOfTheDay(date = date) _dailyImage.value = result _errorMessage.value = null } catch (e: Exception) { _errorMessage.value = "Sin conexión a internet..." _dailyImage.value = null } finally { _isLoading.value = false } } } /** * Guarda una imagen como favorita en Firebase */ fun saveToFavorites(nasaModel: NasaModel) { val userId = firebaseAuth.currentUser?.uid if (userId != null) { viewModelScope.launch { try { // Crear id en la BD 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() _isFavorite.value = true } catch (e: Exception) { _errorMessage.value = "Error al guardar favorito" } } } }}
ViewModels expose immutableStateFlow properties to the UI while keeping mutableMutableStateFlow properties private. This prevents the UI from accidentally modifying state directly.
ViewModels are automatically injected using Hilt’s hiltViewModel() function:
@Composablefun HomeScreen( homeScreenViewModel: HomeScreenViewModel = hiltViewModel(), onNavigateToLogin: () -> Unit) { // ViewModel is automatically created and injected // All dependencies are resolved by Hilt}
The @HiltViewModel annotation tells Hilt to generate the necessary code for ViewModel injection. Dependencies are provided through constructor injection.