Android architecture guide

Material Design Android Development

Learn how to architect, implement, theme, and test modern Android applications using Material Design 3 and Jetpack Compose.

Architecture layers

Layered architecture for Compose apps

UI Layer

Composable screens, stateless components, ViewModels holding immutable UI state, and adaptive scaffolds.

Domain Layer

Reusable use cases encapsulating complex business logic and state operations.

Data Layer

Repositories acting as the single source of truth, backed by local (DataStore, Room) or remote (Ktor, Retrofit) data sources.

Core Design System

Centralized MaterialTheme, color palettes, typography scales, shape tokens, and spring motion definitions.

State management

Unidirectional Data Flow (UDF) with StateFlow

Model UI state as immutable data classes and expose them via StateFlow:

data class ProfileUiState(
    val isLoading: Boolean = false,
    val user: User? = null,
    val errorMessage: String? = null
)

class ProfileViewModel(private val repository: UserRepository) : ViewModel() {
    private val _uiState = MutableStateFlow(ProfileUiState(isLoading = true))
    val uiState: StateFlow<ProfileUiState> = _uiState.asStateFlow()

    fun loadProfile() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }
            try {
                val user = repository.getUser()
                _uiState.update { it.copy(isLoading = false, user = user) }
            } catch (e: Exception) {
                _uiState.update { it.copy(isLoading = false, errorMessage = e.message) }
            }
        }
    }
}

Consume state in composables with collectAsStateWithLifecycle() for lifecycle awareness.

Theming

MaterialTheme wrapper with dynamic color

@Composable
fun AppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }
        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = AppTypography,
        shapes = AppShapes,
        content = content
    )
}

Component design

State hoisting and reusability

  • Separate Stateful & Stateless: Keep reusable UI components stateless (receiving data and emitting event lambdas).
  • Modifier Conventions: Accept a modifier: Modifier = Modifier as the first optional parameter.
  • No Controller Leaks: Never pass NavController or ViewModel instances down to child composables.

Accessibility & UX

Inclusive design standards

  • Touch Targets: Ensure a minimum interactive touch target of 48x48 dp.
  • Semantics: Provide contentDescription for icon buttons and images; merge compound item semantics.
  • Font Scaling: Design layouts that support up to 200% system font enlargement without clipping.
  • Edge-to-Edge: Enable enableEdgeToEdge() and handle window insets gracefully.

Testing & Quality

Automated testing and linting

.\gradlew.bat testDebugUnitTest
.\gradlew.bat lintDebug
.\gradlew.bat :app:assembleDebug