Overview
NASA Explorer follows MVVM (Model-View-ViewModel) architecture with clean separation of concerns. The project is organized into distinct layers for data, domain, and presentation.
Directory Structure
NASAExplorer/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/ccandeladev/nasaexplorer/
│ │ │ │ ├── data/ # Data layer
│ │ │ │ ├── domain/ # Domain models
│ │ │ │ ├── ui/ # Presentation layer
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── NasaExplorerApp.kt
│ │ │ ├── res/ # Android resources
│ │ │ └── AndroidManifest.xml
│ │ ├── androidTest/ # Instrumented tests
│ │ └── test/ # Unit tests
│ ├── build.gradle.kts # App-level build configuration
│ └── google-services.json # Firebase configuration (not in repo)
├── gradle/
│ └── libs.versions.toml # Version catalog
├── build.gradle.kts # Project-level build configuration
├── settings.gradle.kts # Gradle settings
├── local.properties # Local API keys (not in repo)
└── README.md
Package Architecture
The application follows a feature-based package structure within the MVVM pattern.
Data Layer (data/)
Handles all data operations including API calls, authentication, and dependency injection.
api/NasaApiService.kt
api/NasaRepository.kt
// Retrofit service interface for NASA APOD API
interface NasaApiService {
@GET ( "planetary/apod" )
suspend fun getApod (
@Query ( "api_key" ) apiKey: String ,
@Query ( "date" ) date: String ? = null
): NasaResponse
}
Data Package Structure
data/
├── api/
│ ├── NasaApiService.kt # Retrofit API interface
│ ├── NasaRepository.kt # Data repository
│ └── NasaResponse.kt # API response models
├── auth/
│ ├── AuthService.kt # Firebase authentication
│ └── AuthNetworkModule.kt # Auth dependency injection
└── di/
├── ApiNetworkModule.kt # Retrofit + OkHttp configuration
└── FirebaseModule.kt # Firebase DI module
Domain Layer (domain/)
Contains business logic and domain models that represent the core entities of the application.
domain/NasaModel.kt
domain/FavoriteNasaModel.kt
// Main domain model for NASA APOD images
data class NasaModel (
val title: String ,
val explanation: String ,
val url: String ,
val date: String ,
val mediaType: String
)
UI Layer (ui/)
Implements the presentation layer using Jetpack Compose with screen-specific ViewModels.
UI Package Structure
ui/
├── core/
│ ├── NasaExplorerNav.kt # Navigation graph
│ └── Routes.kt # Navigation routes (type-safe)
├── splashscreen/
│ ├── SplashScreen.kt
│ └── SplashViewModel.kt
├── loginscreen/
│ ├── LoginScreen.kt
│ └── LoginScreenViewModel.kt
├── signupscreen/
│ ├── SignupScreen.kt
│ └── SignUpViewModel.kt
├── homescreen/
│ ├── HomeScreen.kt
│ └── HomeScreenViewModel.kt
├── dailyimagescreen/
│ ├── DailyImageScreen.kt
│ └── DailyImageViewModel.kt
├── randomimagescreen/
│ ├── RandomImageScreen.kt
│ └── RandomImageViewModel.kt
├── rangeimagesscreen/
│ ├── RangeImagesScreen.kt
│ └── RangeImagesViewModel.kt
├── favoritesscreen/
│ ├── FavoritesScreen.kt
│ └── FavoritesViewModel.kt
└── theme/
└── Theme.kt # Material Design 3 theme
Each screen follows the pattern of having a Composable function for the UI and a ViewModel for state management, ensuring clear separation of concerns.
Key Application Files
MainActivity.kt
The single activity that hosts the Compose UI.
@AndroidEntryPoint
class MainActivity : ComponentActivity () {
override fun onCreate (savedInstanceState: Bundle ?) {
super . onCreate (savedInstanceState)
setContent {
NasaExplorerTheme {
NasaExplorerApp ()
}
}
}
}
NasaExplorerApp.kt
The root composable that sets up navigation and app structure.
@Composable
fun NasaExplorerApp () {
val navController = rememberNavController ()
NasaExplorerNav (navController = navController)
}
Build Configuration
The project uses Gradle Kotlin DSL for build configuration.
Project-level build.gradle.kts
Defines plugins applied to all modules:
plugins {
alias (libs.plugins.android.application) apply false
alias (libs.plugins.jetbrains.kotlin.android) apply false
alias (libs.plugins.hilt) apply false
alias (libs.plugins.compose.compiler) apply false
id ( "com.google.gms.google-services" ) version "4.4.2" apply false
}
App-level build.gradle.kts
Configures the application module with key settings:
Android Configuration
Dependencies
android {
namespace = "com.ccandeladev.nasaexplorer"
compileSdk = 34
defaultConfig {
applicationId = "com.ccandeladev.nasaexplorer"
minSdk = 28
targetSdk = 34
versionCode = 1
versionName = "1.0"
// API key injection from local.properties
buildConfigField ( "String" , "NASA_API_KEY" , " \" $nasaApiKey \" " )
}
}
Dependency Injection
The project uses Hilt for dependency injection across all layers.
Module Structure
ApiNetworkModule
Provides Retrofit instance configured with:
Base URL for NASA API
Gson converter for JSON parsing
OkHttp client with interceptors
AuthNetworkModule
Provides Firebase Authentication instance for user management
FirebaseModule
Provides Firebase Realtime Database reference for storing favorites
Navigation Architecture
The app uses type-safe navigation with Kotlin serialization:
@Serializable
sealed class Routes {
@Serializable object Splash : Routes ()
@Serializable object Login : Routes ()
@Serializable object SignUp : Routes ()
@Serializable object Home : Routes ()
@Serializable object DailyImage : Routes ()
@Serializable object RandomImage : Routes ()
@Serializable object RangeImages : Routes ()
@Serializable object Favorites : Routes ()
}
Type-safe navigation eliminates string-based route errors and provides compile-time safety for navigation arguments.
Resource Organization
Android resources are organized in the standard structure:
res/
├── drawable/ # Images and vector drawables
├── values/
│ ├── colors.xml # Color definitions
│ ├── strings.xml # String resources
│ └── themes.xml # Material theme configuration
└── mipmap/ # App icons (various densities)
The application uses Material Design 3 components throughout the UI.
Version Catalog
Dependencies are centralized in gradle/libs.versions.toml:
[ versions ]
kotlin = "2.0.0"
hilt = "2.49"
retrofit = "2.9.0"
navigationCompose = "2.8.0"
[ libraries ]
dagger-hilt = { group = "com.google.dagger" , name = "hilt-android" , version.ref = "hilt" }
retrofit2-retrofit = { module = "com.squareup.retrofit2:retrofit" , version.ref = "retrofit" }
[ plugins ]
hilt = { id = "com.google.dagger.hilt.android" , version.ref = "hilt" }
This approach ensures consistent dependency versions across the project and simplifies updates.