DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Android Multi-Module Architecture with Material3 Design System

Android Multi-Module Architecture & Material3 Component Guide

Building scalable Android apps requires more than just stacking screens in a single module. This guide covers why multi-module architecture matters and how to leverage Material3 components effectively.

Why Multi-Module Architecture?

1. Build Time Optimization

Splitting your app into modules allows the build system to compile only what changed, dramatically reducing build times during development. A single monolithic module forces recompilation of the entire project.

// Instead of one massive module:
// app/
//   └── src/main/java/com/example/...  [1000+ files]

// Use modular structure:
// app/
// core/
// feature-auth/
// feature-dashboard/
// feature-settings/
Enter fullscreen mode Exit fullscreen mode

2. Improved Testability

Isolated modules are easier to test in isolation. You can unit test a feature module without loading the entire app.

// feature-auth/build.gradle.kts
dependencies {
    implementation(project(":core"))
    testImplementation("junit:junit:4.13.2")
    testImplementation("org.mockito:mockito-core:5.3.0")
}
Enter fullscreen mode Exit fullscreen mode

3. Team Development Scalability

Multiple teams can work on different feature modules simultaneously without constant merge conflicts. Clear module boundaries enforce separation of concerns.

4. Code Reusability

Modules can be shared across projects. A core-ui module containing design system components becomes a library usable in multiple apps.


Basic Multi-Module Structure

Recommended Architecture

MyApp/
├── app/                    # Application layer (launcher, navigation)
├── core/                   # Shared utilities, networking, storage
│   ├── core-common/        # Utilities, extensions, constants
│   ├── core-network/       # HTTP client, API definitions
│   └── core-database/      # Room entities, DAOs
├── feature-auth/           # Authentication feature
├── feature-dashboard/      # Dashboard feature
├── feature-profile/        # User profile feature
└── build-logic/            # Convention Plugins
Enter fullscreen mode Exit fullscreen mode

Module Responsibilities

  • app/: Entry point, navigation graph, app-level configuration
  • core-common/: Strings, constants, utility functions, extension functions
  • core-network/: Retrofit, Ktor, or OkHttp configuration
  • core-database/: Room database setup, DAOs, entities
  • feature-/**: Screen, ViewModel, repository specific to that feature

Dependency Direction (The Golden Rule)

Feature modules can depend on core, but core cannot depend on features.

app ──→ feature-auth ──→ core
        feature-dashboard ──→ core
        feature-profile ──→ core
Enter fullscreen mode Exit fullscreen mode

Never:

core ──→ feature-auth  ❌ (violates layering)
Enter fullscreen mode Exit fullscreen mode

This prevents circular dependencies and keeps core generic.


Convention Plugins for Standardization

Convention Plugins (in build-logic/) define shared configurations, eliminating repetition:

// build-logic/src/main/kotlin/com.example.android.library.gradle.kts
plugins {
    id("com.android.library")
    kotlin("android")
}

android {
    compileSdk = 34
    defaultConfig {
        minSdk = 24
    }
}

dependencies {
    implementation("androidx.appcompat:appcompat:1.6.1")
    testImplementation("junit:junit:4.13.2")
}
Enter fullscreen mode Exit fullscreen mode

Then in feature modules:

// feature-auth/build.gradle.kts
plugins {
    id("com.example.android.library")
}

dependencies {
    implementation(project(":core-common"))
}
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Single source of truth for SDK versions
  • Consistent dependencies across all modules
  • Changes to build config cascade automatically

Material3 Components Overview

Material3 is Google's latest design system. Here's what you need to know:

Buttons

Elevated Button (highest prominence)

ElevatedButton(onClick = { /* action */ }) {
    Text("Elevated Button")
}
Enter fullscreen mode Exit fullscreen mode

Filled Button (default, recommended)

Button(onClick = { /* action */ }) {
    Text("Filled Button")
}
Enter fullscreen mode Exit fullscreen mode

Outlined Button (secondary action)

OutlinedButton(onClick = { /* action */ }) {
    Text("Outlined Button")
}
Enter fullscreen mode Exit fullscreen mode

Text Button (least prominent)

TextButton(onClick = { /* action */ }) {
    Text("Text Button")
}
Enter fullscreen mode Exit fullscreen mode

Cards

Elevated Card (surface elevation + shadow)

ElevatedCard(
    modifier = Modifier
        .fillMaxWidth()
        .padding(16.dp)
) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text("Card Title", style = MaterialTheme.typography.headlineSmall)
        Text("Card content goes here")
    }
}
Enter fullscreen mode Exit fullscreen mode

Outlined Card (border instead of shadow)

OutlinedCard(
    modifier = Modifier
        .fillMaxWidth()
        .padding(16.dp)
) {
    Text("Outlined Card Content")
}
Enter fullscreen mode Exit fullscreen mode

Chips

Input Chip (user selections)

InputChip(
    selected = isSelected,
    onClick = { isSelected = !isSelected },
    label = { Text("Select Option") }
)
Enter fullscreen mode Exit fullscreen mode

Filter Chip (filtering)

FilterChip(
    selected = isActive,
    onClick = { isActive = !isActive },
    label = { Text("Filter") }
)
Enter fullscreen mode Exit fullscreen mode

Suggestion Chip (recommendations)

SuggestionChip(
    onClick = { /* action */ },
    label = { Text("Suggestion") }
)
Enter fullscreen mode Exit fullscreen mode

Floating Action Button (FAB)

Extended FAB (icon + text)

ExtendedFloatingActionButton(
    text = { Text("Create") },
    icon = { Icon(Icons.Default.Add, contentDescription = null) },
    onClick = { /* action */ }
)
Enter fullscreen mode Exit fullscreen mode

Standard FAB (icon only)

FloatingActionButton(
    onClick = { /* action */ },
) {
    Icon(Icons.Default.Add, contentDescription = "Add")
}
Enter fullscreen mode Exit fullscreen mode

Switches & Checkboxes

Switch

var enabled by remember { mutableStateOf(false) }
Switch(
    checked = enabled,
    onCheckedChange = { enabled = it }
)
Enter fullscreen mode Exit fullscreen mode

Checkbox

var checked by remember { mutableStateOf(false) }
Checkbox(
    checked = checked,
    onCheckedChange = { checked = it }
)
Enter fullscreen mode Exit fullscreen mode

Progress Indicators

Linear Progress

LinearProgressIndicator(
    progress = { 0.75f },
    modifier = Modifier.fillMaxWidth()
)
Enter fullscreen mode Exit fullscreen mode

Circular Progress

CircularProgressIndicator(
    progress = { 0.75f }
)
Enter fullscreen mode Exit fullscreen mode

Putting It Together: Feature Module Example

// feature-auth/src/main/kotlin/com/example/auth/LoginScreen.kt
@Composable
fun LoginScreen(onLoginSuccess: () -> Unit) {
    var email by remember { mutableStateOf("") }
    var password by remember { mutableStateOf("") }
    var isLoading by remember { mutableStateOf(false) }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(24.dp),
        verticalArrangement = Arrangement.Center
    ) {
        // Email Input
        OutlinedTextField(
            value = email,
            onValueChange = { email = it },
            label = { Text("Email") },
            modifier = Modifier.fillMaxWidth()
        )

        Spacer(modifier = Modifier.height(16.dp))

        // Password Input
        OutlinedTextField(
            value = password,
            onValueChange = { password = it },
            label = { Text("Password") },
            visualTransformation = PasswordVisualTransformation(),
            modifier = Modifier.fillMaxWidth()
        )

        Spacer(modifier = Modifier.height(24.dp))

        // Login Button
        Button(
            onClick = { /* validate & login */ },
            modifier = Modifier.fillMaxWidth(),
            enabled = !isLoading
        ) {
            if (isLoading) {
                CircularProgressIndicator(
                    modifier = Modifier.size(20.dp),
                    color = MaterialTheme.colorScheme.onPrimary
                )
            } else {
                Text("Login")
            }
        }

        Spacer(modifier = Modifier.height(16.dp))

        // Sign Up Link
        TextButton(
            onClick = { /* navigate to signup */ },
            modifier = Modifier.align(Alignment.CenterHorizontally)
        ) {
            Text("Don't have an account? Sign up")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Keep modules focused: One feature = one module
  2. Use provided theme: Don't hardcode colors; use MaterialTheme.colorScheme
  3. Test modules independently: Each module should have tests
  4. Document dependencies: Create a dependency graph diagram for team reference
  5. Use ViewModel for state: Don't manage UI state in composables

Next Steps

  • Implement multi-module structure in your next project
  • Adopt Convention Plugins for consistency
  • Explore Material3 themes for your brand colors
  • Set up Compose Preview for each feature module

8 Android App Templates to accelerate your multi-module architecture setup → https://myougatheax.gumroad.com

Top comments (0)