Skip to main content

Overview

NASA Explorer uses Jetpack Compose as its UI toolkit, embracing a fully declarative approach to building Android user interfaces. Compose eliminates the need for XML layouts and provides a modern, reactive way to create beautiful and responsive UIs.
Jetpack Compose is Android’s modern toolkit for building native UI. It simplifies and accelerates UI development with less code, powerful tools, and intuitive Kotlin APIs.

Key Features in NASA Explorer

Declarative UI

Define UI components as functions that describe how the UI should look based on the current state

Material Design 3

Leverage Material 3 components for a modern, consistent design system

Navigation

Type-safe navigation with Compose Navigation and Kotlinx Serialization

State Management

Reactive state management with StateFlow and ViewModel integration

Configuration

Jetpack Compose is configured in the build.gradle.kts file:
build.gradle.kts
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.jetbrains.kotlin.android)
    alias(libs.plugins.compose.compiler)  // Compiler for Kotlin 2.0.0
}

android {
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.1"
    }
}

dependencies {
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.ui)
    implementation(libs.androidx.ui.graphics)
    implementation(libs.androidx.ui.tooling.preview)
    implementation(libs.androidx.material3)
    implementation(libs.androidx.material.icons.extended)
}

MainActivity with Compose

The app’s entry point sets up Compose with Material 3 theming:
MainActivity.kt
package com.ccandeladev.nasaexplorer

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.navigation.compose.rememberNavController
import com.ccandeladev.nasaexplorer.ui.core.NasaExplorerNav
import com.ccandeladev.nasaexplorer.ui.theme.NASAExplorerTheme
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NASAExplorerTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    val navController = rememberNavController()
                    NasaExplorerNav(navHostController = navController)
                }
            }
        }
    }
}
Notice how setContent replaces traditional setContentView() - this is the Compose way of defining UI.

Building Screens with Compose

The home screen implements a Material 3 bottom navigation bar:
HomeScreen.kt
@Composable
fun HomeBottomBar(navController: NavController) {
    var selectedItem by remember { mutableIntStateOf(-1) }

    NavigationBar(
        containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.8f)
    ) {
        NavigationBarItem(
            selected = selectedItem == 1,
            onClick = {
                if (selectedItem != 1) {
                    selectedItem = 1
                    navController.navigate(Routes.DailyImage) {
                        popUpTo<Routes.Home> { inclusive = false }
                    }
                }
            },
            icon = {
                Icon(
                    imageVector = Icons.Filled.Today,
                    contentDescription = "Daily Image",
                    tint = MaterialTheme.colorScheme.onPrimary
                )
            },
            label = { Text(text = "Diaria") },
            colors = NavigationBarItemDefaults.colors(
                indicatorColor = MaterialTheme.colorScheme.secondary,
                selectedIconColor = MaterialTheme.colorScheme.onSecondary,
                selectedTextColor = MaterialTheme.colorScheme.onSecondary
            )
        )
    }
}

Layout Composition

Compose uses layout composables to structure UI elements:
HomeScreen.kt
@Composable
fun HomeScreenContent() {
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Image(
            painter = painterResource(id = R.drawable.fondo),
            contentDescription = "Background",
            modifier = Modifier.fillMaxSize(),
            contentScale = ContentScale.Crop
        )
        Column(
            Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Row(
                Modifier
                    .fillMaxWidth()
                    .padding(bottom = 24.dp),
                horizontalArrangement = Arrangement.SpaceEvenly
            ) {
                Image(
                    painter = painterResource(id = R.drawable.imagen1),
                    contentDescription = "Image 1",
                    modifier = Modifier
                        .size(160.dp)
                        .clip(RoundedCornerShape(16.dp))
                        .border(
                            BorderStroke(1.dp, MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)),
                            shape = RoundedCornerShape(16.dp)
                        ),
                    contentScale = ContentScale.Crop
                )
            }
        }
    }
}

Material Design 3 Theme

Custom Material 3 theme with dark color scheme:
Theme.kt
private val DarkColorScheme = darkColorScheme(
    primary = Color(0xFF919CCA),      // Space blue
    secondary = Color(0xFFB4ACAC),    // Light gray contrast
    tertiary = Color(0XFFFFD740),     // Stellar yellow
    background = Color(0xFF13131B),   // Deep space black
    surface = Color(0xFF222430),      // Dark surface
    onPrimary = Color(0xFF042042),    // Text on primary
    onSecondary = Color.Black,        // Text on secondary
    onBackground = Color.White
)

@Composable
fun NASAExplorerTheme(
    darkTheme: Boolean = true,
    dynamicColor: Boolean = false,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        darkTheme -> DarkColorScheme
        else -> DarkColorScheme
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}

State Management

Compose integrates seamlessly with ViewModels and StateFlow:
DailyImageScreen.kt
@Composable
fun DailyImageScreen(dailyImageViewModel: DailyImageViewModel = hiltViewModel()) {
    // Trigger data loading when screen first composes
    LaunchedEffect(Unit) {
        dailyImageViewModel.loadDailyImage()
    }

    // Subscribe to ViewModel state changes
    val dailyImage by dailyImageViewModel.dailyImage.collectAsState()
    val errorMessage by dailyImageViewModel.errorMessage.collectAsState()
    val isLoading by dailyImageViewModel.isLoading.collectAsState()

    Box(Modifier.fillMaxSize()) {
        when {
            isLoading -> {
                CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
            }
            errorMessage != null -> {
                Text(
                    text = "$errorMessage",
                    color = MaterialTheme.colorScheme.onBackground,
                    textAlign = TextAlign.Center,
                    modifier = Modifier.align(Alignment.Center).padding(24.dp)
                )
            }
            dailyImage != null -> {
                LazyColumn(
                    Modifier
                        .fillMaxSize()
                        .background(color = MaterialTheme.colorScheme.background)
                        .padding(top = 16.dp),
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    item {
                        dailyImage?.let { nasaModel ->
                            dailyImageViewModel.checkIsFavorite(nasaModel.url)
                            ImageItem(
                                nasaModel = nasaModel,
                                dailyImageViewModel = dailyImageViewModel
                            )
                        }
                    }
                }
            }
        }
    }
}

Image Loading with Coil

Compose integration with Coil for async image loading:
DailyImageScreen.kt
AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data(nasaModel.url)
        .crossfade(1000)
        .build(),
    contentDescription = nasaModel.title,
    modifier = Modifier
        .fillMaxWidth()
        .height(300.dp)
        .padding(top = 24.dp)
        .clip(RoundedCornerShape(16.dp))
        .clickable { },
    contentScale = ContentScale.Crop,
    placeholder = painterResource(id = R.drawable.placeholder),
    error = painterResource(id = R.drawable.placeholder)
)

Benefits in NASA Explorer

Compose eliminates the need for findViewById, XML layouts, and view binding. UI components are defined as simple Kotlin functions.
The UI automatically updates when state changes, eliminating manual view updates and reducing bugs.
Kotlin’s type system catches UI errors at compile time rather than runtime.
Compose UI components can be tested in isolation without requiring Android framework dependencies.

Best Practices

1

Keep Composables Pure

Composables should be side-effect free. Use LaunchedEffect for side effects like network calls.
2

Hoist State

Move state up to the appropriate level to make composables reusable and testable.
3

Use remember Wisely

Use remember to preserve state across recompositions, but be mindful of memory leaks.
4

Leverage Material 3

Use Material 3 components for consistent design and accessibility out of the box.

Resources

Jetpack Compose Docs

Official Android documentation for Jetpack Compose

Material Design 3

Material Design 3 guidelines and components

Compose Samples

Official Google samples showcasing Compose capabilities

Compose Pathways

Learn Compose through Google’s guided pathways

Build docs developers (and LLMs) love